summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp22
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java53
-rw-r--r--core/api/current.txt57
-rw-r--r--core/api/system-current.txt46
-rw-r--r--core/api/test-current.txt1
-rw-r--r--core/java/android/accounts/AccountManager.java20
-rw-r--r--core/java/android/app/Activity.java29
-rw-r--r--core/java/android/app/ActivityManager.java87
-rw-r--r--core/java/android/app/ForegroundServiceTypePolicy.java45
-rw-r--r--core/java/android/app/IActivityManager.aidl3
-rw-r--r--core/java/android/app/IActivityTaskManager.aidl4
-rw-r--r--core/java/android/app/Notification.java55
-rw-r--r--core/java/android/app/NotificationManager.java16
-rw-r--r--core/java/android/app/ResourcesManager.java120
-rw-r--r--core/java/android/app/SystemServiceRegistry.java21
-rw-r--r--core/java/android/app/TaskInfo.java16
-rw-r--r--core/java/android/app/jank/FrameOverrunHistogram.java2
-rw-r--r--core/java/android/app/jank/JankDataProcessor.java74
-rw-r--r--core/java/android/app/jank/JankTracker.java7
-rw-r--r--core/java/android/app/jank/StateTracker.java6
-rw-r--r--core/java/android/app/notification.aconfig7
-rw-r--r--core/java/android/content/Context.java24
-rw-r--r--core/java/android/content/ContextWrapper.java1
-rw-r--r--core/java/android/content/pm/ActivityInfo.java10
-rw-r--r--core/java/android/content/pm/PackageManager.java10
-rw-r--r--core/java/android/content/pm/ServiceInfo.java10
-rw-r--r--core/java/android/content/res/ApkAssets.java33
-rw-r--r--core/java/android/content/res/ResourcesImpl.java22
-rw-r--r--core/java/android/content/res/flags.aconfig9
-rw-r--r--core/java/android/hardware/contexthub/HubEndpointInfo.java2
-rw-r--r--core/java/android/hardware/input/IInputManager.aidl2
-rw-r--r--core/java/android/hardware/input/InputManager.java17
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java42
-rw-r--r--core/java/android/os/BinderProxy.java34
-rw-r--r--core/java/android/os/CombinedMessageQueue/MessageQueue.java5
-rw-r--r--core/java/android/os/IBinder.java39
-rw-r--r--core/java/android/os/PerformanceHintManager.java5
-rw-r--r--core/java/android/os/RemoteCallbackList.java46
-rw-r--r--core/java/android/os/flags.aconfig9
-rw-r--r--core/java/android/permission/flags.aconfig9
-rw-r--r--core/java/android/provider/Settings.java23
-rw-r--r--core/java/android/security/authenticationpolicy/AuthenticationPolicyManager.java237
-rw-r--r--core/java/android/security/authenticationpolicy/DisableSecureLockDeviceParams.aidl21
-rw-r--r--core/java/android/security/authenticationpolicy/DisableSecureLockDeviceParams.java82
-rw-r--r--core/java/android/security/authenticationpolicy/EnableSecureLockDeviceParams.aidl21
-rw-r--r--core/java/android/security/authenticationpolicy/EnableSecureLockDeviceParams.java82
-rw-r--r--core/java/android/security/authenticationpolicy/IAuthenticationPolicyService.aidl32
-rw-r--r--core/java/android/security/authenticationpolicy/OWNERS1
-rw-r--r--core/java/android/service/carrier/CarrierMessagingService.java450
-rw-r--r--core/java/android/service/notification/ZenModeConfig.java2
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig12
-rw-r--r--core/java/android/window/flags/window_surfaces.aconfig8
-rw-r--r--core/jni/android_os_PerformanceHintManager.cpp9
-rw-r--r--core/res/Android.bp2
-rw-r--r--core/res/AndroidManifest.xml36
-rw-r--r--core/res/res/layout/notification_2025_template_heads_up_base.xml66
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/src/com/android/frameworks/coretests/bfscctestapp/BfsccTestAppCmdService.java3
-rw-r--r--core/tests/coretests/src/android/os/BinderFrozenStateChangeNotificationTest.java4
-rw-r--r--core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java3
-rw-r--r--core/tests/systemproperties/src/android/os/SystemPropertiesTest.java7
-rw-r--r--data/etc/privapp-permissions-platform.xml2
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml2
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml8
-rw-r--r--libs/WindowManager/Shell/res/values/strings.xml2
-rw-r--r--libs/WindowManager/Shell/res/values/styles.xml1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java167
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/SplitDragPolicy.java45
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/anim/TwoFiftyFiftyTargetAnimator.kt12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java45
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java26
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java928
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageOrderOperator.kt185
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java64
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/ThemeUtils.kt1
-rw-r--r--libs/WindowManager/Shell/tests/OWNERS1
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MinimizeAutoPipAppWindowTest.kt27
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt3
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAutoPipAppWindow.kt72
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeWindowOnAppOpen.kt26
-rw-r--r--libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/Android.bp38
-rw-r--r--libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/AndroidManifest.xml85
-rw-r--r--libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/AndroidTestTemplate.xml97
-rw-r--r--libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/trace_config/trace_config.textproto71
-rw-r--r--libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartAppMediaProjectionFromSplitScreenTest.kt27
-rw-r--r--libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartAppMediaProjectionInSplitScreenTest.kt27
-rw-r--r--libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartAppMediaProjectionTest.kt27
-rw-r--r--libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartAppMediaProjectionWithExtraIntentTest.kt27
-rw-r--r--libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartRecentAppMediaProjectionFromSplitScreenTest.kt27
-rw-r--r--libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartRecentAppMediaProjectionInSplitScreenTest.kt27
-rw-r--r--libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartRecentAppMediaProjectionTest.kt27
-rw-r--r--libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjection.kt76
-rw-r--r--libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionFromSplitScreen.kt83
-rw-r--r--libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionInSplitScreen.kt86
-rw-r--r--libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithExtraIntent.kt86
-rw-r--r--libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartRecentAppMediaProjection.kt76
-rw-r--r--libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartRecentAppMediaProjectionFromSplitScreen.kt83
-rw-r--r--libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartRecentAppMediaProjectionInSplitScreen.kt86
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MediaProjectionUtils.kt6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java86
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/SplitDragPolicyTest.java11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java9
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java28
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java14
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java5
-rw-r--r--libs/hwui/DeviceInfo.h1
-rw-r--r--libs/hwui/RenderNode.cpp2
-rw-r--r--libs/hwui/RenderProperties.h3
-rw-r--r--libs/hwui/renderthread/CanvasContext.cpp5
-rw-r--r--libs/hwui/tests/unit/RenderPropertiesTests.cpp8
-rw-r--r--media/java/android/media/AudioDevicePort.java5
-rw-r--r--media/java/android/media/MediaCodecInfo.java2
-rw-r--r--media/java/android/media/MediaFormat.java4
-rw-r--r--media/java/android/media/quality/IMediaQualityManager.aidl2
-rw-r--r--media/java/android/media/quality/MediaQualityManager.java11
-rw-r--r--media/java/android/media/tv/tuner/frontend/FrontendStatus.java16
-rw-r--r--media/java/android/media/tv/tuner/frontend/StandardExtension.java (renamed from media/java/android/media/tv/tuner/frontend/StandardExt.java)24
-rw-r--r--media/jni/android_media_tv_Tuner.cpp6
-rw-r--r--native/android/libandroid.map.txt3
-rw-r--r--native/android/performance_hint.cpp74
-rw-r--r--nfc/api/current.txt3
-rw-r--r--nfc/api/system-current.txt2
-rw-r--r--nfc/java/android/nfc/INfcAdapter.aidl1
-rw-r--r--nfc/java/android/nfc/INfcOemExtensionCallback.aidl1
-rw-r--r--nfc/java/android/nfc/NfcAdapter.java51
-rw-r--r--nfc/java/android/nfc/NfcOemExtension.java15
-rw-r--r--nfc/java/android/nfc/flags.aconfig8
-rw-r--r--packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java175
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/ModifierExt.kt33
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt17
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt21
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java14
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java9
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java36
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/WritableNamespaces.java38
-rw-r--r--packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java3
-rw-r--r--packages/Shell/AndroidManifest.xml5
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig14
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt3
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt15
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt8
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt3
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt73
-rw-r--r--packages/SystemUI/customization/res/values-land/dimens.xml (renamed from libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/res/xml/network_security_config.xml)13
-rw-r--r--packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml1
-rw-r--r--packages/SystemUI/customization/res/values/dimens.xml1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt50
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/development/data/repository/DevelopmentSettingRepositoryTest.kt167
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/development/domain/interactor/BuildNumberInteractorTest.kt138
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt35
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt247
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt286
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt99
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt103
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt26
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModelTest.kt13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt23
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt24
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt37
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerTest.kt50
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt77
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java15
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractorTest.kt24
-rw-r--r--packages/SystemUI/res/values-land/dimens.xml1
-rw-r--r--packages/SystemUI/res/values-sw600dp-land/dimens.xml1
-rw-r--r--packages/SystemUI/res/values/dimens.xml4
-rw-r--r--packages/SystemUI/res/values/strings.xml3
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/development/data/repository/DevelopmentSettingRepository.kt79
-rw-r--r--packages/SystemUI/src/com/android/systemui/development/domain/interactor/BuildNumberInteractor.kt89
-rw-r--r--packages/SystemUI/src/com/android/systemui/development/shared/model/BuildNumber.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/development/ui/compose/BuildNumber.kt81
-rw-r--r--packages/SystemUI/src/com/android/systemui/development/ui/viewmodel/BuildNumberViewModel.kt66
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestures.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt59
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/qualifiers/CustomShortcutCategories.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/qualifiers/DefaultShortcutCategories.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/KeyCombination.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/INoteTaskBubblesService.aidl4
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubbleExpandBehavior.aidl3
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubbleExpandBehavior.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubblesController.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskLaunchMode.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/shared/model/WakeSleepReason.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModule.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt112
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddable.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileDefaultsRepository.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTilePackageUpdatesRepository.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileDataInteractor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java42
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractor.kt17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt31
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java38
-rw-r--r--packages/SystemUI/tests/utils/src/android/content/ClipboardManagerKosmos.kt22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/development/data/repository/DevelopmentSettingRepositoryKosmos.kt25
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/development/domain/interactor/BuildNumberInteractorKosmos.kt35
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/development/ui/viewmodel/BuildNumberViewModelKosmos.kt29
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt9
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt2
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java6
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java49
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java67
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java15
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java4
-rw-r--r--ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java47
-rw-r--r--ravenwood/tests/bivalentinst/Android.bp7
-rw-r--r--ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_nonself.java7
-rw-r--r--ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_self.java9
-rw-r--r--ravenwood/tests/bivalenttest/Android.bp2
-rw-r--r--ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodConfigTest.java10
-rw-r--r--ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/compat/RavenwoodCompatFrameworkTest.kt12
-rw-r--r--ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java9
-rw-r--r--ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerTestBase.java3
-rw-r--r--ravenwood/tests/runtime-test/Android.bp2
-rw-r--r--services/autofill/java/com/android/server/autofill/Helper.java5
-rw-r--r--services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java318
-rw-r--r--services/backup/java/com/android/server/backup/BackupManagerService.java6
-rw-r--r--services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java3
-rw-r--r--services/backup/java/com/android/server/backup/UserBackupManagerService.java266
-rw-r--r--services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java2
-rw-r--r--services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java6
-rw-r--r--services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java5
-rw-r--r--services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java23
-rw-r--r--services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java7
-rw-r--r--services/companion/java/com/android/server/companion/securechannel/SecureChannel.java4
-rw-r--r--services/core/java/com/android/server/accounts/AccountManagerService.java25
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java9
-rw-r--r--services/core/java/com/android/server/am/AppStartInfoTracker.java25
-rw-r--r--services/core/java/com/android/server/am/SettingsToPropertiesMapper.java1
-rw-r--r--services/core/java/com/android/server/am/flags.aconfig10
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java5
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java5
-rw-r--r--services/core/java/com/android/server/input/NativeInputManagerService.java5
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java2
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java7
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubService.java11
-rw-r--r--services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java16
-rw-r--r--services/core/java/com/android/server/media/quality/MediaQualityService.java6
-rw-r--r--services/core/java/com/android/server/security/authenticationpolicy/AuthenticationPolicyService.java49
-rw-r--r--services/core/java/com/android/server/security/authenticationpolicy/SecureLockDeviceService.java120
-rw-r--r--services/core/java/com/android/server/security/authenticationpolicy/SecureLockDeviceServiceInternal.java55
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java37
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java5
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java27
-rw-r--r--services/core/java/com/android/server/wm/AppCompatCameraOverrides.java24
-rw-r--r--services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java31
-rw-r--r--services/core/java/com/android/server/wm/DisplayArea.java2
-rw-r--r--services/core/java/com/android/server/wm/RecentTasks.java13
-rw-r--r--services/core/java/com/android/server/wm/Task.java64
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java38
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java8
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java19
-rw-r--r--services/core/java/com/android/server/wm/WindowStateAnimator.java7
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp7
-rw-r--r--services/java/com/android/server/SystemServer.java12
-rw-r--r--services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java10
-rw-r--r--services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java33
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java8
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/backup/BackupAgentConnectionManagerTest.java381
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java216
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java11
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java8
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java104
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/WakelockPowerStatsCollectorTest.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/security/authenticationpolicy/AuthenticationPolicyServiceTest.java18
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java14
-rw-r--r--services/tests/wmtests/AndroidManifest.xml4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java19
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java24
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java100
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java58
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java32
-rw-r--r--test-mock/src/android/test/mock/MockContentResolver.java3
-rw-r--r--tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java92
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt8
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/StartMediaProjectionAppHelper.kt44
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml18
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/StartMediaProjectionActivity.java18
344 files changed, 8708 insertions, 2786 deletions
diff --git a/Android.bp b/Android.bp
index 54cb2684068d..49a6a2b3ec65 100644
--- a/Android.bp
+++ b/Android.bp
@@ -103,10 +103,10 @@ filegroup {
":android.hardware.gnss-V2-java-source",
":android.hardware.graphics.common-V3-java-source",
":android.hardware.keymaster-V4-java-source",
- ":android.hardware.radio-V3-java-source",
- ":android.hardware.radio.data-V3-java-source",
- ":android.hardware.radio.network-V3-java-source",
- ":android.hardware.radio.voice-V3-java-source",
+ ":android.hardware.radio-V4-java-source",
+ ":android.hardware.radio.data-V4-java-source",
+ ":android.hardware.radio.network-V4-java-source",
+ ":android.hardware.radio.voice-V4-java-source",
":android.hardware.security.secureclock-V1-java-source",
":android.hardware.thermal-V3-java-source",
":android.hardware.tv.tuner-V3-java-source",
@@ -232,13 +232,13 @@ java_library {
"android.hardware.gnss-V2.1-java",
"android.hardware.health-V1.0-java-constants",
"android.hardware.radio-V1.6-java",
- "android.hardware.radio.data-V3-java",
- "android.hardware.radio.ims-V2-java",
- "android.hardware.radio.messaging-V3-java",
- "android.hardware.radio.modem-V3-java",
- "android.hardware.radio.network-V3-java",
- "android.hardware.radio.sim-V3-java",
- "android.hardware.radio.voice-V3-java",
+ "android.hardware.radio.data-V4-java",
+ "android.hardware.radio.ims-V3-java",
+ "android.hardware.radio.messaging-V4-java",
+ "android.hardware.radio.modem-V4-java",
+ "android.hardware.radio.network-V4-java",
+ "android.hardware.radio.sim-V4-java",
+ "android.hardware.radio.voice-V4-java",
"android.hardware.thermal-V1.0-java-constants",
"android.hardware.thermal-V1.0-java",
"android.hardware.thermal-V1.1-java",
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index 8bd3ef4f4d1a..637c726a9bd1 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -36,10 +36,14 @@ import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.UidObserver;
+import android.app.compat.CompatChanges;
import android.app.job.JobInfo;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManagerInternal;
import android.app.usage.UsageStatsManagerInternal.UsageEventListener;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
+import android.compat.annotation.Overridable;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
@@ -132,6 +136,27 @@ public final class QuotaController extends StateController {
return (int) (val ^ (val >>> 32));
}
+ /**
+ * When enabled this change id overrides the default quota policy enforcement to the jobs
+ * running in the foreground process state.
+ */
+ // TODO: b/379681266 - Might need some refactoring for a better app-compat strategy.
+ @VisibleForTesting
+ @ChangeId
+ @Disabled // Disabled by default
+ @Overridable // The change can be overridden in user build
+ static final long OVERRIDE_QUOTA_ENFORCEMENT_TO_FGS_JOBS = 341201311L;
+
+ /**
+ * When enabled this change id overrides the default quota policy enforcement policy
+ * the jobs started when app was in the TOP state.
+ */
+ @VisibleForTesting
+ @ChangeId
+ @Disabled // Disabled by default
+ @Overridable // The change can be overridden in user build.
+ static final long OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS = 374323858L;
+
@VisibleForTesting
static class ExecutionStats {
/**
@@ -622,7 +647,9 @@ public final class QuotaController extends StateController {
}
final int uid = jobStatus.getSourceUid();
- if (!Flags.enforceQuotaPolicyToTopStartedJobs() && mTopAppCache.get(uid)) {
+ if ((!Flags.enforceQuotaPolicyToTopStartedJobs()
+ || CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS,
+ uid)) && mTopAppCache.get(uid)) {
if (DEBUG) {
Slog.d(TAG, jobStatus.toShortString() + " is top started job");
}
@@ -659,7 +686,9 @@ public final class QuotaController extends StateController {
timer.stopTrackingJob(jobStatus);
}
}
- if (!Flags.enforceQuotaPolicyToTopStartedJobs()) {
+ if (!Flags.enforceQuotaPolicyToTopStartedJobs()
+ || CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS,
+ jobStatus.getSourceUid())) {
mTopStartedJobs.remove(jobStatus);
}
}
@@ -772,7 +801,13 @@ public final class QuotaController extends StateController {
/** @return true if the job was started while the app was in the TOP state. */
private boolean isTopStartedJobLocked(@NonNull final JobStatus jobStatus) {
- return !Flags.enforceQuotaPolicyToTopStartedJobs() && mTopStartedJobs.contains(jobStatus);
+ if (!Flags.enforceQuotaPolicyToTopStartedJobs()
+ || CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS,
+ jobStatus.getSourceUid())) {
+ return mTopStartedJobs.contains(jobStatus);
+ }
+
+ return false;
}
/** Returns the maximum amount of time this job could run for. */
@@ -2634,9 +2669,13 @@ public final class QuotaController extends StateController {
}
@VisibleForTesting
- int getProcessStateQuotaFreeThreshold() {
- return Flags.enforceQuotaPolicyToFgsJobs() ? ActivityManager.PROCESS_STATE_BOUND_TOP :
- ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+ int getProcessStateQuotaFreeThreshold(int uid) {
+ if (Flags.enforceQuotaPolicyToFgsJobs()
+ && !CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_FGS_JOBS, uid)) {
+ return ActivityManager.PROCESS_STATE_BOUND_TOP;
+ }
+
+ return ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
}
private class QcHandler extends Handler {
@@ -2776,7 +2815,7 @@ public final class QuotaController extends StateController {
isQuotaFree = true;
} else {
final boolean reprocess;
- if (procState <= getProcessStateQuotaFreeThreshold()) {
+ if (procState <= getProcessStateQuotaFreeThreshold(uid)) {
reprocess = !mForegroundUids.get(uid);
mForegroundUids.put(uid, true);
isQuotaFree = true;
diff --git a/core/api/current.txt b/core/api/current.txt
index 81c85a9dc8c0..76f3e5a3cd39 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -3830,7 +3830,7 @@ package android.accounts {
method @RequiresPermission(value="android.permission.AUTHENTICATE_ACCOUNTS", apis="..22") public boolean notifyAccountAuthenticated(android.accounts.Account);
method @RequiresPermission(value="android.permission.AUTHENTICATE_ACCOUNTS", apis="..22") public String peekAuthToken(android.accounts.Account, String);
method @Deprecated @RequiresPermission(value="android.permission.MANAGE_ACCOUNTS", apis="..22") public android.accounts.AccountManagerFuture<java.lang.Boolean> removeAccount(android.accounts.Account, android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler);
- method @RequiresPermission(value="android.permission.MANAGE_ACCOUNTS", apis="..22") public android.accounts.AccountManagerFuture<android.os.Bundle> removeAccount(android.accounts.Account, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
+ method @FlaggedApi("android.app.admin.flags.split_create_managed_profile_enabled") @RequiresPermission(value="android.permission.REMOVE_ACCOUNTS", conditional=true) public android.accounts.AccountManagerFuture<android.os.Bundle> removeAccount(android.accounts.Account, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
method @RequiresPermission(value="android.permission.AUTHENTICATE_ACCOUNTS", apis="..22") public boolean removeAccountExplicitly(android.accounts.Account);
method public void removeOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener);
method @RequiresPermission(value="android.permission.AUTHENTICATE_ACCOUNTS", apis="..22") public android.accounts.AccountManagerFuture<android.accounts.Account> renameAccount(android.accounts.Account, @Size(min=1) String, android.accounts.AccountManagerCallback<android.accounts.Account>, android.os.Handler);
@@ -4614,6 +4614,7 @@ package android.app {
method public void reportFullyDrawn();
method public android.view.DragAndDropPermissions requestDragAndDropPermissions(android.view.DragEvent);
method public void requestFullscreenMode(int, @Nullable android.os.OutcomeReceiver<java.lang.Void,java.lang.Throwable>);
+ method @FlaggedApi("com.android.window.flags.enable_desktop_windowing_app_to_web_education") public final void requestOpenInBrowserEducation();
method public final void requestPermissions(@NonNull String[], int);
method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public final void requestPermissions(@NonNull String[], int, int);
method public final void requestShowKeyboardShortcuts();
@@ -4639,7 +4640,6 @@ package android.app {
method public void setInheritShowWhenLocked(boolean);
method public void setIntent(android.content.Intent);
method @FlaggedApi("android.security.content_uri_permission_apis") public void setIntent(@Nullable android.content.Intent, @Nullable android.app.ComponentCaller);
- method @FlaggedApi("com.android.window.flags.enable_desktop_windowing_app_to_web_education") public final void setLimitSystemEducationDialogs(boolean);
method public void setLocusContext(@Nullable android.content.LocusId, @Nullable android.os.Bundle);
method public final void setMediaController(android.media.session.MediaController);
method public void setPictureInPictureParams(@NonNull android.app.PictureInPictureParams);
@@ -13399,6 +13399,7 @@ package android.content.pm {
field public static final String FEATURE_BACKUP = "android.software.backup";
field public static final String FEATURE_BLUETOOTH = "android.hardware.bluetooth";
field public static final String FEATURE_BLUETOOTH_LE = "android.hardware.bluetooth_le";
+ field @FlaggedApi("com.android.ranging.flags.ranging_cs_enabled") public static final String FEATURE_BLUETOOTH_LE_CHANNEL_SOUNDING = "android.hardware.bluetooth_le.channel_sounding";
field public static final String FEATURE_CAMERA = "android.hardware.camera";
field public static final String FEATURE_CAMERA_ANY = "android.hardware.camera.any";
field public static final String FEATURE_CAMERA_AR = "android.hardware.camera.ar";
@@ -13808,7 +13809,7 @@ package android.content.pm {
field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_CAMERA}, anyOf={android.Manifest.permission.CAMERA}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_CAMERA = 64; // 0x40
field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE}, anyOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.CHANGE_NETWORK_STATE, android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.CHANGE_WIFI_MULTICAST_STATE, android.Manifest.permission.NFC, android.Manifest.permission.TRANSMIT_IR, android.Manifest.permission.UWB_RANGING, android.Manifest.permission.RANGING}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 16; // 0x10
field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_DATA_SYNC = 1; // 0x1
- field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_HEALTH}, anyOf={android.Manifest.permission.ACTIVITY_RECOGNITION, android.Manifest.permission.BODY_SENSORS, android.Manifest.permission.HIGH_SAMPLING_RATE_SENSORS}) public static final int FOREGROUND_SERVICE_TYPE_HEALTH = 256; // 0x100
+ field @FlaggedApi("android.permission.flags.replace_body_sensor_permission_enabled") @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_HEALTH}, anyOf={android.Manifest.permission.ACTIVITY_RECOGNITION, android.Manifest.permission.HIGH_SAMPLING_RATE_SENSORS, android.health.connect.HealthPermissions.READ_HEART_RATE, android.health.connect.HealthPermissions.READ_SKIN_TEMPERATURE, android.health.connect.HealthPermissions.READ_OXYGEN_SATURATION}) public static final int FOREGROUND_SERVICE_TYPE_HEALTH = 256; // 0x100
field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_LOCATION}, anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_LOCATION = 8; // 0x8
field public static final int FOREGROUND_SERVICE_TYPE_MANIFEST = -1; // 0xffffffff
field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK = 2; // 0x2
@@ -21213,6 +21214,7 @@ package android.inputmethodservice {
method public void setExtractView(android.view.View);
method public void setExtractViewShown(boolean);
method public void setInputView(android.view.View);
+ method @FlaggedApi("android.view.inputmethod.adaptive_handwriting_bounds") public final void setStylusHandwritingRegion(@NonNull android.graphics.Region);
method public final void setStylusHandwritingSessionTimeout(@NonNull java.time.Duration);
method public final boolean shouldOfferSwitchingToNextInputMethod();
method public void showStatusIcon(@DrawableRes int);
@@ -23189,7 +23191,6 @@ package android.media {
method public boolean isVendor();
field @FlaggedApi("android.media.codec.in_process_sw_audio_codec") public static final int SECURITY_MODEL_MEMORY_SAFE = 1; // 0x1
field @FlaggedApi("android.media.codec.in_process_sw_audio_codec") public static final int SECURITY_MODEL_SANDBOXED = 0; // 0x0
- field @FlaggedApi("android.media.codec.in_process_sw_audio_codec") public static final int SECURITY_MODEL_TRUSTED_CONTENT_ONLY = 2; // 0x2
}
public static final class MediaCodecInfo.AudioCapabilities {
@@ -24080,7 +24081,6 @@ package android.media {
field public static final int COLOR_TRANSFER_ST2084 = 6; // 0x6
field @FlaggedApi("android.media.codec.in_process_sw_audio_codec") public static final int FLAG_SECURITY_MODEL_MEMORY_SAFE = 2; // 0x2
field @FlaggedApi("android.media.codec.in_process_sw_audio_codec") public static final int FLAG_SECURITY_MODEL_SANDBOXED = 1; // 0x1
- field @FlaggedApi("android.media.codec.in_process_sw_audio_codec") public static final int FLAG_SECURITY_MODEL_TRUSTED_CONTENT_ONLY = 4; // 0x4
field public static final String KEY_AAC_DRC_ALBUM_MODE = "aac-drc-album-mode";
field public static final String KEY_AAC_DRC_ATTENUATION_FACTOR = "aac-drc-cut-level";
field public static final String KEY_AAC_DRC_BOOST_FACTOR = "aac-drc-boost-level";
@@ -33809,7 +33809,7 @@ package android.os {
}
public interface IBinder {
- method @FlaggedApi("android.os.binder_frozen_state_change_callback") public default void addFrozenStateChangeCallback(@NonNull android.os.IBinder.FrozenStateChangeCallback) throws android.os.RemoteException;
+ method @FlaggedApi("android.os.binder_frozen_state_change_callback") public default void addFrozenStateChangeCallback(@NonNull java.util.concurrent.Executor, @NonNull android.os.IBinder.FrozenStateChangeCallback) throws android.os.RemoteException;
method public void dump(@NonNull java.io.FileDescriptor, @Nullable String[]) throws android.os.RemoteException;
method public void dumpAsync(@NonNull java.io.FileDescriptor, @Nullable String[]) throws android.os.RemoteException;
method @Nullable public String getInterfaceDescriptor() throws android.os.RemoteException;
@@ -34440,6 +34440,7 @@ package android.os {
method public void finishBroadcast();
method public Object getBroadcastCookie(int);
method public E getBroadcastItem(int);
+ method @FlaggedApi("android.os.binder_frozen_state_change_callback") @Nullable public java.util.concurrent.Executor getExecutor();
method @FlaggedApi("android.os.binder_frozen_state_change_callback") public int getFrozenCalleePolicy();
method @FlaggedApi("android.os.binder_frozen_state_change_callback") public int getMaxQueueSize();
method public Object getRegisteredCallbackCookie(int);
@@ -34460,6 +34461,7 @@ package android.os {
@FlaggedApi("android.os.binder_frozen_state_change_callback") public static final class RemoteCallbackList.Builder<E extends android.os.IInterface> {
ctor public RemoteCallbackList.Builder(int);
method @NonNull public android.os.RemoteCallbackList<E> build();
+ method @NonNull public android.os.RemoteCallbackList.Builder setExecutor(@NonNull java.util.concurrent.Executor);
method @NonNull public android.os.RemoteCallbackList.Builder setInterfaceDiedCallback(@NonNull android.os.RemoteCallbackList.Builder.InterfaceDiedCallback<E>);
method @NonNull public android.os.RemoteCallbackList.Builder setMaxQueueSize(int);
}
@@ -41179,6 +41181,18 @@ package android.service.carrier {
method @Deprecated public void onSendTextSms(@NonNull String, int, @NonNull String, @NonNull android.service.carrier.CarrierMessagingService.ResultCallback<android.service.carrier.CarrierMessagingService.SendSmsResult>);
method public void onSendTextSms(@NonNull String, int, @NonNull String, int, @NonNull android.service.carrier.CarrierMessagingService.ResultCallback<android.service.carrier.CarrierMessagingService.SendSmsResult>);
field public static final int DOWNLOAD_STATUS_ERROR = 2; // 0x2
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int DOWNLOAD_STATUS_MMS_ERROR_CONFIGURATION_ERROR = 606; // 0x25e
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int DOWNLOAD_STATUS_MMS_ERROR_DATA_DISABLED = 610; // 0x262
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int DOWNLOAD_STATUS_MMS_ERROR_HTTP_FAILURE = 603; // 0x25b
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int DOWNLOAD_STATUS_MMS_ERROR_INACTIVE_SUBSCRIPTION = 609; // 0x261
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int DOWNLOAD_STATUS_MMS_ERROR_INVALID_APN = 601; // 0x259
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int DOWNLOAD_STATUS_MMS_ERROR_INVALID_SUBSCRIPTION_ID = 608; // 0x260
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int DOWNLOAD_STATUS_MMS_ERROR_IO_ERROR = 604; // 0x25c
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int DOWNLOAD_STATUS_MMS_ERROR_MMS_DISABLED_BY_CARRIER = 611; // 0x263
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int DOWNLOAD_STATUS_MMS_ERROR_NO_DATA_NETWORK = 607; // 0x25f
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int DOWNLOAD_STATUS_MMS_ERROR_RETRY = 605; // 0x25d
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int DOWNLOAD_STATUS_MMS_ERROR_UNABLE_CONNECT_MMS = 602; // 0x25a
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int DOWNLOAD_STATUS_MMS_ERROR_UNSPECIFIED = 600; // 0x258
field public static final int DOWNLOAD_STATUS_OK = 0; // 0x0
field public static final int DOWNLOAD_STATUS_RETRY_ON_CARRIER_NETWORK = 1; // 0x1
field public static final int RECEIVE_OPTIONS_DEFAULT = 0; // 0x0
@@ -41186,7 +41200,38 @@ package android.service.carrier {
field public static final int RECEIVE_OPTIONS_SKIP_NOTIFY_WHEN_CREDENTIAL_PROTECTED_STORAGE_UNAVAILABLE = 2; // 0x2
field public static final int SEND_FLAG_REQUEST_DELIVERY_STATUS = 1; // 0x1
field public static final int SEND_STATUS_ERROR = 2; // 0x2
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_MMS_ERROR_CONFIGURATION_ERROR = 406; // 0x196
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_MMS_ERROR_DATA_DISABLED = 410; // 0x19a
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_MMS_ERROR_HTTP_FAILURE = 403; // 0x193
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_MMS_ERROR_INACTIVE_SUBSCRIPTION = 409; // 0x199
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_MMS_ERROR_INVALID_APN = 401; // 0x191
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_MMS_ERROR_INVALID_SUBSCRIPTION_ID = 408; // 0x198
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_MMS_ERROR_IO_ERROR = 404; // 0x194
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_MMS_ERROR_MMS_DISABLED_BY_CARRIER = 411; // 0x19b
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_MMS_ERROR_NO_DATA_NETWORK = 407; // 0x197
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_MMS_ERROR_RETRY = 405; // 0x195
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_MMS_ERROR_UNABLE_CONNECT_MMS = 402; // 0x192
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_MMS_ERROR_UNSPECIFIED = 400; // 0x190
field public static final int SEND_STATUS_OK = 0; // 0x0
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_CANCELLED = 215; // 0xd7
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_ENCODING_ERROR = 212; // 0xd4
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_ERROR_FDN_CHECK_FAILURE = 204; // 0xcc
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_ERROR_GENERIC_FAILURE = 200; // 0xc8
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_ERROR_LIMIT_EXCEEDED = 203; // 0xcb
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_ERROR_NO_SERVICE = 202; // 0xca
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_ERROR_NULL_PDU = 201; // 0xc9
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED = 206; // 0xce
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_ERROR_SHORT_CODE_NOT_ALLOWED = 205; // 0xcd
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_INVALID_ARGUMENTS = 208; // 0xd0
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_INVALID_SMSC_ADDRESS = 213; // 0xd5
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_INVALID_SMS_FORMAT = 210; // 0xd2
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_INVALID_STATE = 209; // 0xd1
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_NETWORK_ERROR = 211; // 0xd3
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_NETWORK_REJECT = 207; // 0xcf
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_OPERATION_NOT_ALLOWED = 214; // 0xd6
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_REQUEST_NOT_SUPPORTED = 216; // 0xd8
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_SMS_BLOCKED_DURING_EMERGENCY = 217; // 0xd9
+ field @FlaggedApi("com.android.internal.telephony.flags.temporary_failures_in_carrier_messaging_service") public static final int SEND_STATUS_RESULT_SMS_SEND_RETRY_FAILED = 218; // 0xda
field public static final int SEND_STATUS_RETRY_ON_CARRIER_NETWORK = 1; // 0x1
field public static final String SERVICE_INTERFACE = "android.service.carrier.CarrierMessagingService";
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index bc0037d9318f..a171f3dc4c17 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -103,6 +103,7 @@ package android {
field public static final String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE";
field public static final String BROADCAST_CLOSE_SYSTEM_DIALOGS = "android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS";
field @Deprecated public static final String BROADCAST_NETWORK_PRIVILEGED = "android.permission.BROADCAST_NETWORK_PRIVILEGED";
+ field @FlaggedApi("android.media.audio.concurrent_audio_record_bypass_permission") public static final String BYPASS_CONCURRENT_RECORD_AUDIO_RESTRICTION = "android.permission.BYPASS_CONCURRENT_RECORD_AUDIO_RESTRICTION";
field public static final String BYPASS_ROLE_QUALIFICATION = "android.permission.BYPASS_ROLE_QUALIFICATION";
field public static final String CALL_AUDIO_INTERCEPTION = "android.permission.CALL_AUDIO_INTERCEPTION";
field public static final String CAMERA_DISABLE_TRANSMIT_LED = "android.permission.CAMERA_DISABLE_TRANSMIT_LED";
@@ -134,6 +135,7 @@ package android {
field public static final String CONTROL_KEYGUARD_SECURE_NOTIFICATIONS = "android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS";
field public static final String CONTROL_OEM_PAID_NETWORK_PREFERENCE = "android.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE";
field public static final String CONTROL_VPN = "android.permission.CONTROL_VPN";
+ field @FlaggedApi("android.app.admin.flags.split_create_managed_profile_enabled") public static final String COPY_ACCOUNTS = "android.permission.COPY_ACCOUNTS";
field public static final String CREATE_USERS = "android.permission.CREATE_USERS";
field public static final String CREATE_VIRTUAL_DEVICE = "android.permission.CREATE_VIRTUAL_DEVICE";
field public static final String CRYPT_KEEPER = "android.permission.CRYPT_KEEPER";
@@ -229,6 +231,7 @@ package android {
field public static final String MANAGE_ROTATION_RESOLVER = "android.permission.MANAGE_ROTATION_RESOLVER";
field public static final String MANAGE_SAFETY_CENTER = "android.permission.MANAGE_SAFETY_CENTER";
field public static final String MANAGE_SEARCH_UI = "android.permission.MANAGE_SEARCH_UI";
+ field @FlaggedApi("android.security.secure_lockdown") public static final String MANAGE_SECURE_LOCK_DEVICE = "android.permission.MANAGE_SECURE_LOCK_DEVICE";
field public static final String MANAGE_SENSOR_PRIVACY = "android.permission.MANAGE_SENSOR_PRIVACY";
field public static final String MANAGE_SMARTSPACE = "android.permission.MANAGE_SMARTSPACE";
field public static final String MANAGE_SOUND_TRIGGER = "android.permission.MANAGE_SOUND_TRIGGER";
@@ -345,6 +348,7 @@ package android {
field public static final String REGISTER_SIM_SUBSCRIPTION = "android.permission.REGISTER_SIM_SUBSCRIPTION";
field public static final String REGISTER_STATS_PULL_ATOM = "android.permission.REGISTER_STATS_PULL_ATOM";
field public static final String REMOTE_DISPLAY_PROVIDER = "android.permission.REMOTE_DISPLAY_PROVIDER";
+ field @FlaggedApi("android.app.admin.flags.split_create_managed_profile_enabled") public static final String REMOVE_ACCOUNTS = "android.permission.REMOVE_ACCOUNTS";
field public static final String REMOVE_DRM_CERTIFICATES = "android.permission.REMOVE_DRM_CERTIFICATES";
field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
field public static final String RENOUNCE_PERMISSIONS = "android.permission.RENOUNCE_PERMISSIONS";
@@ -570,6 +574,7 @@ package android.accessibilityservice {
package android.accounts {
public class AccountManager {
+ method @FlaggedApi("android.app.admin.flags.split_create_managed_profile_enabled") @NonNull @RequiresPermission(anyOf={android.Manifest.permission.COPY_ACCOUNTS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public android.accounts.AccountManagerFuture<java.lang.Boolean> copyAccountToUser(@NonNull android.accounts.Account, @NonNull android.os.UserHandle, @NonNull android.os.UserHandle, @Nullable android.accounts.AccountManagerCallback<java.lang.Boolean>, @Nullable android.os.Handler);
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public android.accounts.AccountManagerFuture<android.os.Bundle> finishSessionAsUser(android.os.Bundle, android.app.Activity, android.os.UserHandle, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
}
@@ -3869,6 +3874,7 @@ package android.content {
field public static final String APP_INTEGRITY_SERVICE = "app_integrity";
field public static final String APP_PREDICTION_SERVICE = "app_prediction";
field public static final String AUDIO_DEVICE_VOLUME_SERVICE = "audio_device_volume";
+ field @FlaggedApi("android.security.secure_lockdown") public static final String AUTHENTICATION_POLICY_SERVICE = "authentication_policy";
field public static final String BACKUP_SERVICE = "backup";
field public static final String BATTERY_STATS_SERVICE = "batterystats";
field public static final int BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS = 1048576; // 0x100000
@@ -9893,7 +9899,7 @@ package android.media.tv.tuner.frontend {
method public int getSignalStrength();
method public int getSnr();
method public int getSpectralInversion();
- method @FlaggedApi("android.media.tv.flags.tuner_w_apis") @NonNull public android.media.tv.tuner.frontend.StandardExt getStandardExt();
+ method @FlaggedApi("android.media.tv.flags.tuner_w_apis") @NonNull public android.media.tv.tuner.frontend.StandardExtension getStandardExtension();
method @NonNull public int[] getStreamIds();
method public int getSymbolRate();
method @IntRange(from=0, to=65535) public int getSystemId();
@@ -9948,7 +9954,7 @@ package android.media.tv.tuner.frontend {
field public static final int FRONTEND_STATUS_TYPE_SIGNAL_STRENGTH = 6; // 0x6
field public static final int FRONTEND_STATUS_TYPE_SNR = 1; // 0x1
field public static final int FRONTEND_STATUS_TYPE_SPECTRAL = 10; // 0xa
- field @FlaggedApi("android.media.tv.flags.tuner_w_apis") public static final int FRONTEND_STATUS_TYPE_STANDARD_EXT = 47; // 0x2f
+ field @FlaggedApi("android.media.tv.flags.tuner_w_apis") public static final int FRONTEND_STATUS_TYPE_STANDARD_EXTENSION = 47; // 0x2f
field public static final int FRONTEND_STATUS_TYPE_STREAM_IDS = 39; // 0x27
field public static final int FRONTEND_STATUS_TYPE_SYMBOL_RATE = 7; // 0x7
field public static final int FRONTEND_STATUS_TYPE_T2_SYSTEM_ID = 29; // 0x1d
@@ -10240,9 +10246,9 @@ package android.media.tv.tuner.frontend {
method public default void onUnlocked();
}
- @FlaggedApi("android.media.tv.flags.tuner_w_apis") public final class StandardExt {
- method public int getDvbsStandardExt();
- method public int getDvbtStandardExt();
+ @FlaggedApi("android.media.tv.flags.tuner_w_apis") public final class StandardExtension {
+ method public int getDvbsStandardExtension();
+ method public int getDvbtStandardExtension();
}
}
@@ -12858,6 +12864,36 @@ package android.security.advancedprotection {
}
+package android.security.authenticationpolicy {
+
+ @FlaggedApi("android.security.secure_lockdown") public final class AuthenticationPolicyManager {
+ method @FlaggedApi("android.security.secure_lockdown") @RequiresPermission(android.Manifest.permission.MANAGE_SECURE_LOCK_DEVICE) public int disableSecureLockDevice(@NonNull android.security.authenticationpolicy.DisableSecureLockDeviceParams);
+ method @FlaggedApi("android.security.secure_lockdown") @RequiresPermission(android.Manifest.permission.MANAGE_SECURE_LOCK_DEVICE) public int enableSecureLockDevice(@NonNull android.security.authenticationpolicy.EnableSecureLockDeviceParams);
+ field @FlaggedApi("android.security.secure_lockdown") public static final int ERROR_ALREADY_ENABLED = 6; // 0x6
+ field @FlaggedApi("android.security.secure_lockdown") public static final int ERROR_INSUFFICIENT_BIOMETRICS = 5; // 0x5
+ field @FlaggedApi("android.security.secure_lockdown") public static final int ERROR_INVALID_PARAMS = 3; // 0x3
+ field @FlaggedApi("android.security.secure_lockdown") public static final int ERROR_NO_BIOMETRICS_ENROLLED = 4; // 0x4
+ field @FlaggedApi("android.security.secure_lockdown") public static final int ERROR_UNKNOWN = 0; // 0x0
+ field @FlaggedApi("android.security.secure_lockdown") public static final int ERROR_UNSUPPORTED = 2; // 0x2
+ field @FlaggedApi("android.security.secure_lockdown") public static final int SUCCESS = 1; // 0x1
+ }
+
+ @FlaggedApi("android.security.secure_lockdown") public final class DisableSecureLockDeviceParams implements android.os.Parcelable {
+ ctor public DisableSecureLockDeviceParams(@NonNull String);
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.security.authenticationpolicy.DisableSecureLockDeviceParams> CREATOR;
+ }
+
+ @FlaggedApi("android.security.secure_lockdown") public final class EnableSecureLockDeviceParams implements android.os.Parcelable {
+ ctor public EnableSecureLockDeviceParams(@NonNull String);
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.security.authenticationpolicy.EnableSecureLockDeviceParams> CREATOR;
+ }
+
+}
+
package android.security.forensic {
@FlaggedApi("android.security.afl_api") public class ForensicManager {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 44bcc2a737b9..ad1d937a3d10 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1805,6 +1805,7 @@ package android.hardware.input {
method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void remapModifierKey(int, int);
method @FlaggedApi("com.android.input.flags.device_associations") @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void removeUniqueIdAssociationByDescriptor(@NonNull String);
method @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void removeUniqueIdAssociationByPort(@NonNull String);
+ method public void resetLockedModifierState();
field public static final long BLOCK_UNTRUSTED_TOUCHES = 158002302L; // 0x96aec7eL
}
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 87acbbf65b2f..72450999993d 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -16,9 +16,13 @@
package android.accounts;
+import static android.Manifest.permission.COPY_ACCOUNTS;
+import static android.Manifest.permission.REMOVE_ACCOUNTS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.app.admin.flags.Flags.FLAG_SPLIT_CREATE_MANAGED_PROFILE_ENABLED;
import android.annotation.BroadcastBehavior;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -26,6 +30,7 @@ import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.Size;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.UserHandleAware;
@@ -1312,7 +1317,8 @@ public class AccountManager {
* {@link AccountManagerFuture} must not be used on the main thread.
*
* <p>This method requires the caller to have a signature match with the
- * authenticator that manages the specified account.
+ * authenticator that manages the specified account, be a profile owner or have the
+ * {@link android.Manifest.permission#REMOVE_ACCOUNTS} permission.
*
* <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
* MANAGE_ACCOUNTS permission is needed for those platforms. See docs for
@@ -1344,6 +1350,8 @@ public class AccountManager {
* </ul>
*/
@UserHandleAware
+ @RequiresPermission(value = REMOVE_ACCOUNTS, conditional = true)
+ @FlaggedApi(FLAG_SPLIT_CREATE_MANAGED_PROFILE_ENABLED)
public AccountManagerFuture<Bundle> removeAccount(final Account account,
final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
return removeAccountAsUser(account, activity, callback, handler, mContext.getUser());
@@ -2019,9 +2027,15 @@ public class AccountManager {
* succeeded.
* @hide
*/
+ @SuppressLint("SamShouldBeLast")
+ @NonNull
+ @SystemApi
+ @RequiresPermission(anyOf = {COPY_ACCOUNTS, INTERACT_ACROSS_USERS_FULL})
+ @FlaggedApi(FLAG_SPLIT_CREATE_MANAGED_PROFILE_ENABLED)
public AccountManagerFuture<Boolean> copyAccountToUser(
- final Account account, final UserHandle fromUser, final UserHandle toUser,
- AccountManagerCallback<Boolean> callback, Handler handler) {
+ @NonNull final Account account, @NonNull final UserHandle fromUser,
+ @NonNull final UserHandle toUser, @Nullable AccountManagerCallback<Boolean> callback,
+ @Nullable Handler handler) {
if (account == null) throw new IllegalArgumentException("account is null");
if (toUser == null || fromUser == null) {
throw new IllegalArgumentException("fromUser and toUser cannot be null");
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 419eb7dac5f0..38aea64386a0 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1270,27 +1270,22 @@ public class Activity extends ContextThemeWrapper
}
/**
- * To make users aware of system features such as the app header menu and its various
- * functionalities, educational dialogs are shown to demonstrate how to find and utilize these
- * features. Using this method, an activity can specify if it wants these educational dialogs to
- * be shown. When set to {@code true}, these dialogs are not completely blocked; however, the
- * system will be notified that they should not be shown unless necessary. If this API is not
- * called, the system's educational dialogs are not limited by default.
- *
- * <p>This method can be utilized when activities have states where showing an
- * educational dialog would be disruptive to the user. For example, if a game application is
- * expecting prompt user input, this method can be used to limit educational dialogs such as the
- * dialogs that showcase the app header's features which, in this instance, would disrupt the
- * user's experience if shown.</p>
- *
- * <p>Note that educational dialogs may be shown soon after this activity is launched, so
- * this method must be called early if the intent is to limit the dialogs from the start.</p>
+ * Requests to show the “Open in browser” education. “Open in browser” is a feature
+ * within the app header that allows users to switch from an app to the web. The feature
+ * is made available when an application is opened by a user clicking a link or when a
+ * link is provided by an application. Links can be provided by utilizing
+ * {@link AssistContent#EXTRA_AUTHENTICATING_USER_WEB_URI} or
+ * {@link AssistContent#setWebUri}.
+ *
+ * <p>This method should be utilized when an activity wants to nudge the user to switch
+ * to the web application in cases where the web may provide the user with a better
+ * experience. Note that this method does not guarantee that the education will be shown.</p>
*/
@FlaggedApi(com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION)
- public final void setLimitSystemEducationDialogs(boolean limitSystemEducationDialogs) {
+ public final void requestOpenInBrowserEducation() {
try {
ActivityTaskManager
- .getService().setLimitSystemEducationDialogs(mToken, limitSystemEducationDialogs);
+ .getService().requestOpenInBrowserEducation(mToken);
} catch (RemoteException e) {
// Empty
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index ab75069cc5d8..33ba05865042 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -2679,62 +2679,6 @@ public class ActivityManager {
*/
public static class RecentTaskInfo extends TaskInfo implements Parcelable {
/**
- * @hide
- */
- public static class PersistedTaskSnapshotData {
- /**
- * The bounds of the task when the last snapshot was taken, may be null if the task is
- * not yet attached to the hierarchy.
- * @see {@link android.window.TaskSnapshot#mTaskSize}.
- * @hide
- */
- public @Nullable Point taskSize;
-
- /**
- * The content insets of the task when the task snapshot was taken.
- * @see {@link android.window.TaskSnapshot#mContentInsets}.
- * @hide
- */
- public @Nullable Rect contentInsets;
-
- /**
- * The size of the last snapshot taken, may be null if there is no associated snapshot.
- * @see {@link android.window.TaskSnapshot#mSnapshot}.
- * @hide
- */
- public @Nullable Point bufferSize;
-
- /**
- * Sets the data from the other data.
- * @hide
- */
- public void set(PersistedTaskSnapshotData other) {
- taskSize = other.taskSize;
- contentInsets = other.contentInsets;
- bufferSize = other.bufferSize;
- }
-
- /**
- * Sets the data from the provided {@param snapshot}.
- * @hide
- */
- public void set(TaskSnapshot snapshot) {
- if (snapshot == null) {
- taskSize = null;
- contentInsets = null;
- bufferSize = null;
- return;
- }
- final HardwareBuffer buffer = snapshot.getHardwareBuffer();
- taskSize = new Point(snapshot.getTaskSize());
- contentInsets = new Rect(snapshot.getContentInsets());
- bufferSize = buffer != null
- ? new Point(buffer.getWidth(), buffer.getHeight())
- : null;
- }
- }
-
- /**
* If this task is currently running, this is the identifier for it.
* If it is not running, this will be -1.
*
@@ -2770,24 +2714,6 @@ public class ActivityManager {
@Deprecated
public int affiliatedTaskId;
- /**
- * Information of organized child tasks.
- *
- * @deprecated No longer used
- * @hide
- */
- @Deprecated
- public ArrayList<RecentTaskInfo> childrenTaskInfos = new ArrayList<>();
-
- /**
- * Information about the last snapshot taken for this task.
- *
- * @deprecated No longer used
- * @hide
- */
- @Deprecated
- public PersistedTaskSnapshotData lastSnapshotData = new PersistedTaskSnapshotData();
-
public RecentTaskInfo() {
}
@@ -2803,10 +2729,6 @@ public class ActivityManager {
public void readFromParcel(Parcel source) {
id = source.readInt();
persistentId = source.readInt();
- childrenTaskInfos = source.readArrayList(RecentTaskInfo.class.getClassLoader(), android.app.ActivityManager.RecentTaskInfo.class);
- lastSnapshotData.taskSize = source.readTypedObject(Point.CREATOR);
- lastSnapshotData.contentInsets = source.readTypedObject(Rect.CREATOR);
- lastSnapshotData.bufferSize = source.readTypedObject(Point.CREATOR);
super.readTaskFromParcel(source);
}
@@ -2814,10 +2736,6 @@ public class ActivityManager {
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeInt(persistentId);
- dest.writeList(childrenTaskInfos);
- dest.writeTypedObject(lastSnapshotData.taskSize, flags);
- dest.writeTypedObject(lastSnapshotData.contentInsets, flags);
- dest.writeTypedObject(lastSnapshotData.bufferSize, flags);
super.writeTaskToParcel(dest, flags);
}
@@ -2884,11 +2802,6 @@ public class ActivityManager {
pw.println(" }");
}
pw.print(" ");
- pw.print(" lastSnapshotData {");
- pw.print(" taskSize=" + lastSnapshotData.taskSize);
- pw.print(" contentInsets=" + lastSnapshotData.contentInsets);
- pw.print(" bufferSize=" + lastSnapshotData.bufferSize);
- pw.println(" }");
}
}
diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java
index 16444dc5adde..6efc4ef55180 100644
--- a/core/java/android/app/ForegroundServiceTypePolicy.java
+++ b/core/java/android/app/ForegroundServiceTypePolicy.java
@@ -62,6 +62,7 @@ import android.content.pm.ServiceInfo.ForegroundServiceType;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
+import android.health.connect.HealthPermissions;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -484,21 +485,35 @@ public abstract class ForegroundServiceTypePolicy {
*/
public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_HEALTH =
new ForegroundServiceTypePolicyInfo(
- FOREGROUND_SERVICE_TYPE_HEALTH,
- ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
- ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
- new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
- new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_HEALTH)
- }, true),
- new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
- new RegularPermission(Manifest.permission.ACTIVITY_RECOGNITION),
- new RegularPermission(Manifest.permission.BODY_SENSORS),
- new RegularPermission(Manifest.permission.HIGH_SAMPLING_RATE_SENSORS),
- }, false),
- FGS_TYPE_PERM_ENFORCEMENT_FLAG_HEALTH /* permissionEnforcementFlag */,
- true /* permissionEnforcementFlagDefaultValue */,
- false /* foregroundOnlyPermission */
- );
+ FOREGROUND_SERVICE_TYPE_HEALTH,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ new ForegroundServiceTypePermissions(
+ new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_HEALTH)
+ },
+ true),
+ new ForegroundServiceTypePermissions(getAllowedHealthPermissions(), false),
+ FGS_TYPE_PERM_ENFORCEMENT_FLAG_HEALTH /* permissionEnforcementFlag */,
+ true /* permissionEnforcementFlagDefaultValue */,
+ false /* foregroundOnlyPermission */);
+
+ /** Returns the permissions needed for the policy of the health foreground service type. */
+ private static ForegroundServiceTypePermission[] getAllowedHealthPermissions() {
+ final ArrayList<ForegroundServiceTypePermission> permissions = new ArrayList<>();
+ permissions.add(new RegularPermission(Manifest.permission.ACTIVITY_RECOGNITION));
+ permissions.add(new RegularPermission(Manifest.permission.HIGH_SAMPLING_RATE_SENSORS));
+
+ if (android.permission.flags.Flags.replaceBodySensorPermissionEnabled()) {
+ permissions.add(new RegularPermission(HealthPermissions.READ_HEART_RATE));
+ permissions.add(new RegularPermission(HealthPermissions.READ_SKIN_TEMPERATURE));
+ permissions.add(new RegularPermission(HealthPermissions.READ_OXYGEN_SATURATION));
+ } else {
+ permissions.add(new RegularPermission(Manifest.permission.BODY_SENSORS));
+ }
+
+ return permissions.toArray(new ForegroundServiceTypePermission[permissions.size()]);
+ }
/**
* The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING}.
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index a8412fa66609..0668958b2d5c 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -864,7 +864,8 @@ interface IActivityManager {
/**
* Suppress or reenable the rate limit on foreground service notification deferral.
- * This is for use within CTS and is protected by android.permission.WRITE_DEVICE_CONFIG.
+ * This is for use within CTS and is protected by android.permission.WRITE_DEVICE_CONFIG
+ * and WRITE_ALLOWLISTED_DEVICE_CONFIG.
*
* @param enable false to suppress rate-limit policy; true to reenable it.
*/
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index ec7b72ec677e..c6f62a21641d 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -242,8 +242,8 @@ interface IActivityTaskManager {
boolean supportsLocalVoiceInteraction();
- // Sets whether system educational dialogs should be limited
- void setLimitSystemEducationDialogs(IBinder appToken, boolean limitSystemEducationDialogs);
+ // Requests the "Open in browser" education to be shown
+ void requestOpenInBrowserEducation(IBinder appToken);
// Get device configuration
ConfigurationInfo getDeviceConfigurationInfo();
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 8545bd5ade73..b78f11148178 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -814,8 +814,8 @@ public class Notification implements Parcelable
if (Flags.notificationsRedesignTemplates()) {
return switch (layoutId) {
case R.layout.notification_2025_template_collapsed_base,
+ R.layout.notification_2025_template_heads_up_base,
R.layout.notification_2025_template_header,
- R.layout.notification_template_material_heads_up_base,
R.layout.notification_template_material_big_base,
R.layout.notification_template_material_big_picture,
R.layout.notification_template_material_big_text,
@@ -3257,7 +3257,7 @@ public class Notification implements Parcelable
*/
@FlaggedApi(Flags.FLAG_UI_RICH_ONGOING)
public boolean hasPromotableCharacteristics() {
- return isColorized()
+ return isColorizedRequested()
&& hasTitle()
&& !containsCustomViews()
&& hasPromotableStyle();
@@ -4083,6 +4083,12 @@ public class Notification implements Parcelable
flags &= ~FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
}
}
+ if (Flags.apiRichOngoing()) {
+ if ((flags & FLAG_PROMOTED_ONGOING) != 0) {
+ flagStrings.add("PROMOTED_ONGOING");
+ flags &= ~FLAG_PROMOTED_ONGOING;
+ }
+ }
if (android.service.notification.Flags.notificationSilentFlag()) {
if ((flags & FLAG_SILENT) != 0) {
@@ -7517,7 +7523,11 @@ public class Notification implements Parcelable
}
private int getHeadsUpBaseLayoutResource() {
- return R.layout.notification_template_material_heads_up_base;
+ if (Flags.notificationsRedesignTemplates()) {
+ return R.layout.notification_2025_template_heads_up_base;
+ } else {
+ return R.layout.notification_template_material_heads_up_base;
+ }
}
private int getCompactHeadsUpBaseLayoutResource() {
@@ -7788,8 +7798,16 @@ public class Notification implements Parcelable
* @hide
*/
public boolean isColorized() {
- return extras.getBoolean(EXTRA_COLORIZED)
- && (hasColorizedPermission() || isFgsOrUij());
+ return isColorizedRequested()
+ && (hasColorizedPermission() || isFgsOrUij() || isPromotedOngoing());
+ }
+
+ /**
+ * @return true if this notification has requested to be colorized, regardless of whether it
+ * meets the requirements to be displayed that way.
+ */
+ private boolean isColorizedRequested() {
+ return extras.getBoolean(EXTRA_COLORIZED);
}
/**
@@ -7803,6 +7821,19 @@ public class Notification implements Parcelable
}
/**
+ * Returns whether this notification is a promoted ongoing notification.
+ *
+ * This requires the Notification.FLAG_PROMOTED_ONGOING flag to be set
+ * (which may be true once the api_rich_ongoing feature flag is enabled),
+ * and requires that the ui_rich_ongoing feature flag is enabled.
+ *
+ * @hide
+ */
+ public boolean isPromotedOngoing() {
+ return Flags.uiRichOngoing() && (flags & Notification.FLAG_PROMOTED_ONGOING) != 0;
+ }
+
+ /**
* @return true if this is a media style notification with a media session
*
* @hide
@@ -11559,11 +11590,9 @@ public class Notification implements Parcelable
contentView.setBundle(R.id.progress,
"setProgressModel", model.toBundle());
- if (mTrackerIcon != null) {
- contentView.setIcon(R.id.progress,
- "setProgressTrackerIcon",
- mTrackerIcon);
- }
+ contentView.setIcon(R.id.progress,
+ "setProgressTrackerIcon",
+ mTrackerIcon);
return contentView;
}
@@ -11669,8 +11698,10 @@ public class Notification implements Parcelable
return points;
}
- @NonNull
- private NotificationProgressModel createProgressModel(int defaultProgressColor,
+ /**
+ * @hide
+ */
+ public @NonNull NotificationProgressModel createProgressModel(int defaultProgressColor,
int backgroundColor) {
final NotificationProgressModel model;
if (mIndeterminate) {
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index c49b02210dd4..c310f95814dc 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -16,6 +16,8 @@
package android.app;
+import static android.Manifest.permission.POST_NOTIFICATIONS;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.service.notification.Flags.notificationClassification;
import android.annotation.CallbackExecutor;
@@ -1597,11 +1599,15 @@ public class NotificationManager {
* Returns whether notifications from the calling package are enabled.
*/
public boolean areNotificationsEnabled() {
- INotificationManager service = getService();
- try {
- return service.areNotificationsEnabled(mContext.getPackageName());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ if (Flags.nmBinderPerfPermissionCheck()) {
+ return mContext.checkSelfPermission(POST_NOTIFICATIONS) == PERMISSION_GRANTED;
+ } else {
+ INotificationManager service = getService();
+ try {
+ return service.areNotificationsEnabled(mContext.getPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
}
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 087e246e8841..599a46b131d5 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -174,54 +174,22 @@ public class ResourcesManager {
}
/**
- * Apply the registered library paths to the passed AssetManager. If may create a new
- * AssetManager if any changes are needed and it isn't allowed to reuse the old one.
- *
- * @return new AssetManager and the hash code for the current version of the registered paths
+ * Apply the registered library paths to the passed impl object
+ * @return the hash code for the current version of the registered paths
*/
- public @NonNull Pair<AssetManager, Integer> updateResourceImplAssetsWithRegisteredLibs(
- @NonNull AssetManager assets, boolean reuseAssets) {
+ public int updateResourceImplWithRegisteredLibs(@NonNull ResourcesImpl impl) {
if (!Flags.registerResourcePaths()) {
- return new Pair<>(assets, 0);
+ return 0;
}
- final int size;
- final PathCollector collector;
-
- synchronized (mLock) {
- size = mSharedLibAssetsMap.size();
- if (assets == AssetManager.getSystem()) {
- return new Pair<>(assets, size);
- }
- collector = new PathCollector(resourcesKeyFromAssets(assets));
- for (int i = 0; i < size; i++) {
- final var libraryKey = mSharedLibAssetsMap.valueAt(i).getResourcesKey();
- collector.appendKey(libraryKey);
- }
- }
- if (collector.isSameAsOriginal()) {
- return new Pair<>(assets, size);
- }
- if (reuseAssets) {
- assets.addPresetApkKeys(extractApkKeys(collector.collectedKey()));
- return new Pair<>(assets, size);
- }
- final var newAssetsBuilder = new AssetManager.Builder();
- for (final var asset : assets.getApkAssets()) {
- if (!asset.isForLoader()) {
- newAssetsBuilder.addApkAssets(asset);
- }
+ final var collector = new PathCollector(null);
+ final int size = mSharedLibAssetsMap.size();
+ for (int i = 0; i < size; i++) {
+ final var libraryKey = mSharedLibAssetsMap.valueAt(i).getResourcesKey();
+ collector.appendKey(libraryKey);
}
- for (final var key : extractApkKeys(collector.collectedKey())) {
- try {
- final var asset = loadApkAssets(key);
- newAssetsBuilder.addApkAssets(asset);
- } catch (IOException e) {
- Log.e(TAG, "Couldn't load assets for key " + key, e);
- }
- }
- assets.getLoaders().forEach(newAssetsBuilder::addLoader);
- return new Pair<>(newAssetsBuilder.build(), size);
+ impl.getAssets().addPresetApkKeys(extractApkKeys(collector.collectedKey()));
+ return size;
}
public static class ApkKey {
@@ -656,23 +624,6 @@ public class ResourcesManager {
return apkKeys;
}
- private ResourcesKey resourcesKeyFromAssets(@NonNull AssetManager assets) {
- final var libs = new ArrayList<String>();
- final var overlays = new ArrayList<String>();
- for (final ApkAssets asset : assets.getApkAssets()) {
- if (asset.isSystem() || asset.isForLoader()) {
- continue;
- }
- if (asset.isOverlay()) {
- overlays.add(asset.getAssetPath());
- } else if (asset.isSharedLib()) {
- libs.add(asset.getAssetPath());
- }
- }
- return new ResourcesKey(null, null, overlays.toArray(new String[0]),
- libs.toArray(new String[0]), 0, null, null);
- }
-
/**
* Creates an AssetManager from the paths within the ResourcesKey.
*
@@ -801,7 +752,7 @@ public class ResourcesManager {
final Configuration config = generateConfig(key);
final DisplayMetrics displayMetrics = getDisplayMetrics(generateDisplayId(key), daj);
- final ResourcesImpl impl = new ResourcesImpl(assets, displayMetrics, config, daj, true);
+ final ResourcesImpl impl = new ResourcesImpl(assets, displayMetrics, config, daj);
if (DEBUG) {
Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
@@ -1881,32 +1832,31 @@ public class ResourcesManager {
for (int i = 0; i < resourcesCount; i++) {
final WeakReference<Resources> ref = mAllResourceReferences.get(i);
final Resources r = ref != null ? ref.get() : null;
- if (r == null) {
- continue;
- }
- final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
- if (key != null) {
- final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key);
- if (impl == null) {
- throw new Resources.NotFoundException("failed to redirect ResourcesImpl");
- }
- r.setImpl(impl);
- } else {
- // ResourcesKey is null which means the ResourcesImpl could belong to a
- // Resources created by application through Resources constructor and was not
- // managed by ResourcesManager, so the ResourcesImpl needs to be recreated to
- // have shared library asset paths appended if there are any.
- final ResourcesImpl oldImpl = r.getImpl();
- if (oldImpl != null) {
- final AssetManager oldAssets = oldImpl.getAssets();
- // ResourcesImpl constructor will help to append shared library asset paths.
- if (oldAssets != AssetManager.getSystem()) {
- if (oldAssets.isUpToDate()) {
- final ResourcesImpl newImpl = new ResourcesImpl(oldImpl);
+ if (r != null) {
+ final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
+ if (key != null) {
+ final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key);
+ if (impl == null) {
+ throw new Resources.NotFoundException("failed to redirect ResourcesImpl");
+ }
+ r.setImpl(impl);
+ } else {
+ // ResourcesKey is null which means the ResourcesImpl could belong to a
+ // Resources created by application through Resources constructor and was not
+ // managed by ResourcesManager, so the ResourcesImpl needs to be recreated to
+ // have shared library asset paths appended if there are any.
+ if (r.getImpl() != null) {
+ final ResourcesImpl oldImpl = r.getImpl();
+ final AssetManager oldAssets = oldImpl.getAssets();
+ // ResourcesImpl constructor will help to append shared library asset paths.
+ if (oldAssets != AssetManager.getSystem() && oldAssets.isUpToDate()) {
+ final ResourcesImpl newImpl = new ResourcesImpl(oldAssets,
+ oldImpl.getMetrics(), oldImpl.getConfiguration(),
+ oldImpl.getDisplayAdjustments());
r.setImpl(newImpl);
} else {
- Slog.w(TAG, "Skip appending shared library asset paths for "
- + "the Resources as its assets are not up to date.");
+ Slog.w(TAG, "Skip appending shared library asset paths for the "
+ + "Resource as its assets are not up to date.");
}
}
}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 53a7dad76788..6a23349bf8aa 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -241,6 +241,8 @@ import android.security.advancedprotection.AdvancedProtectionManager;
import android.security.advancedprotection.IAdvancedProtectionService;
import android.security.attestationverification.AttestationVerificationManager;
import android.security.attestationverification.IAttestationVerificationManagerService;
+import android.security.authenticationpolicy.AuthenticationPolicyManager;
+import android.security.authenticationpolicy.IAuthenticationPolicyService;
import android.security.forensic.ForensicManager;
import android.security.forensic.IForensicService;
import android.security.keystore.KeyStoreManager;
@@ -1025,6 +1027,25 @@ public final class SystemServiceRegistry {
}
});
+ registerService(Context.AUTHENTICATION_POLICY_SERVICE,
+ AuthenticationPolicyManager.class,
+ new CachedServiceFetcher<AuthenticationPolicyManager>() {
+ @Override
+ public AuthenticationPolicyManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ if (!android.security.Flags.secureLockdown()) {
+ throw new ServiceNotFoundException(
+ Context.AUTHENTICATION_POLICY_SERVICE);
+ }
+
+ final IBinder binder = ServiceManager.getServiceOrThrow(
+ Context.AUTHENTICATION_POLICY_SERVICE);
+ final IAuthenticationPolicyService service =
+ IAuthenticationPolicyService.Stub.asInterface(binder);
+ return new AuthenticationPolicyManager(ctx.getOuterContext(), service);
+ }
+ });
+
registerService(Context.TV_INTERACTIVE_APP_SERVICE, TvInteractiveAppManager.class,
new CachedServiceFetcher<TvInteractiveAppManager>() {
@Override
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index aac963ade726..01cc9d82d56d 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -340,10 +340,10 @@ public class TaskInfo {
public int requestedVisibleTypes;
/**
- * Whether the top activity has requested to limit educational dialogs shown by the system.
+ * The timestamp of the top activity's last request to show the "Open in Browser" education.
* @hide
*/
- public boolean isTopActivityLimitSystemEducationDialogs;
+ public long topActivityRequestOpenInBrowserEducationTimestamp;
/**
* Encapsulate specific App Compat information.
@@ -493,8 +493,8 @@ public class TaskInfo {
&& Objects.equals(capturedLink, that.capturedLink)
&& capturedLinkTimestamp == that.capturedLinkTimestamp
&& requestedVisibleTypes == that.requestedVisibleTypes
- && isTopActivityLimitSystemEducationDialogs
- == that.isTopActivityLimitSystemEducationDialogs
+ && topActivityRequestOpenInBrowserEducationTimestamp
+ == that.topActivityRequestOpenInBrowserEducationTimestamp
&& appCompatTaskInfo.equalsForTaskOrganizer(that.appCompatTaskInfo)
&& Objects.equals(topActivityMainWindowFrame, that.topActivityMainWindowFrame);
}
@@ -571,7 +571,7 @@ public class TaskInfo {
capturedLink = source.readTypedObject(Uri.CREATOR);
capturedLinkTimestamp = source.readLong();
requestedVisibleTypes = source.readInt();
- isTopActivityLimitSystemEducationDialogs = source.readBoolean();
+ topActivityRequestOpenInBrowserEducationTimestamp = source.readLong();
appCompatTaskInfo = source.readTypedObject(AppCompatTaskInfo.CREATOR);
topActivityMainWindowFrame = source.readTypedObject(Rect.CREATOR);
}
@@ -627,7 +627,7 @@ public class TaskInfo {
dest.writeTypedObject(capturedLink, flags);
dest.writeLong(capturedLinkTimestamp);
dest.writeInt(requestedVisibleTypes);
- dest.writeBoolean(isTopActivityLimitSystemEducationDialogs);
+ dest.writeLong(topActivityRequestOpenInBrowserEducationTimestamp);
dest.writeTypedObject(appCompatTaskInfo, flags);
dest.writeTypedObject(topActivityMainWindowFrame, flags);
}
@@ -672,8 +672,8 @@ public class TaskInfo {
+ " capturedLink=" + capturedLink
+ " capturedLinkTimestamp=" + capturedLinkTimestamp
+ " requestedVisibleTypes=" + requestedVisibleTypes
- + " isTopActivityLimitSystemEducationDialogs="
- + isTopActivityLimitSystemEducationDialogs
+ + " topActivityRequestOpenInBrowserEducationTimestamp="
+ + topActivityRequestOpenInBrowserEducationTimestamp
+ " appCompatTaskInfo=" + appCompatTaskInfo
+ " topActivityMainWindowFrame=" + topActivityMainWindowFrame
+ "}";
diff --git a/core/java/android/app/jank/FrameOverrunHistogram.java b/core/java/android/app/jank/FrameOverrunHistogram.java
index e28ac126a90a..3ad6531a46bf 100644
--- a/core/java/android/app/jank/FrameOverrunHistogram.java
+++ b/core/java/android/app/jank/FrameOverrunHistogram.java
@@ -39,7 +39,7 @@ public class FrameOverrunHistogram {
* Create a new instance of FrameOverrunHistogram.
*/
public FrameOverrunHistogram() {
- mBucketCounts = new int[sBucketEndpoints.length - 1];
+ mBucketCounts = new int[sBucketEndpoints.length];
}
/**
diff --git a/core/java/android/app/jank/JankDataProcessor.java b/core/java/android/app/jank/JankDataProcessor.java
index 7525d0402ee4..7ceaeb3fb070 100644
--- a/core/java/android/app/jank/JankDataProcessor.java
+++ b/core/java/android/app/jank/JankDataProcessor.java
@@ -59,7 +59,6 @@ public class JankDataProcessor {
* @param appUid the uid of the app.
*/
public void processJankData(List<JankData> jankData, String activityName, int appUid) {
- mCurrentBatchCount++;
// add all the previous and active states to the pending states list.
mStateTracker.retrieveAllStates(mPendingStates);
@@ -79,9 +78,8 @@ public class JankDataProcessor {
}
}
// At this point we have attributed all frames to a state.
- if (mCurrentBatchCount >= LOG_BATCH_FREQUENCY) {
- logMetricCounts();
- }
+ incrementBatchCountAndMaybeLogStats();
+
// return the StatData object back to the pool to be reused.
jankDataProcessingComplete();
}
@@ -91,7 +89,73 @@ public class JankDataProcessor {
* stats
*/
public void mergeJankStats(AppJankStats jankStats, String activityName) {
- // TODO b/377572463 Add Merging Logic
+ // Each state has a key which is a combination of widget category, widget id and widget
+ // state, this key is also used to identify pending stats, a pending stat is essentially a
+ // state with frames associated with it.
+ String stateKey = mStateTracker.getStateKey(jankStats.getWidgetCategory(),
+ jankStats.getWidgetId(), jankStats.getWidgetState());
+
+ if (mPendingJankStats.containsKey(stateKey)) {
+ mergeExistingStat(stateKey, jankStats);
+ } else {
+ mergeNewStat(stateKey, activityName, jankStats);
+ }
+
+ incrementBatchCountAndMaybeLogStats();
+ }
+
+ private void mergeExistingStat(String stateKey, AppJankStats jankStat) {
+ PendingJankStat pendingStat = mPendingJankStats.get(stateKey);
+
+ pendingStat.mJankyFrames += jankStat.getJankyFrameCount();
+ pendingStat.mTotalFrames += jankStat.getTotalFrameCount();
+
+ mergeOverrunHistograms(pendingStat.mFrameOverrunBuckets,
+ jankStat.getFrameOverrunHistogram().getBucketCounters());
+ }
+
+ private void mergeNewStat(String stateKey, String activityName, AppJankStats jankStats) {
+ // Check if we have space for a new stat
+ if (mPendingJankStats.size() > MAX_IN_MEMORY_STATS) {
+ return;
+ }
+
+ PendingJankStat pendingStat = mPendingJankStatsPool.acquire();
+ if (pendingStat == null) {
+ pendingStat = new PendingJankStat();
+
+ }
+ pendingStat.clearStats();
+
+ pendingStat.mActivityName = activityName;
+ pendingStat.mUid = jankStats.getUid();
+ pendingStat.mWidgetId = jankStats.getWidgetId();
+ pendingStat.mWidgetCategory = jankStats.getWidgetCategory();
+ pendingStat.mWidgetState = jankStats.getWidgetState();
+ pendingStat.mTotalFrames = jankStats.getTotalFrameCount();
+ pendingStat.mJankyFrames = jankStats.getJankyFrameCount();
+
+ mergeOverrunHistograms(pendingStat.mFrameOverrunBuckets,
+ jankStats.getFrameOverrunHistogram().getBucketCounters());
+
+ mPendingJankStats.put(stateKey, pendingStat);
+ }
+
+ private void mergeOverrunHistograms(int[] mergeTarget, int[] mergeSource) {
+ // The length of each histogram should be identical, if they are not then its possible the
+ // buckets are not in sync, these records should not be recorded.
+ if (mergeTarget.length != mergeSource.length) return;
+
+ for (int i = 0; i < mergeTarget.length; i++) {
+ mergeTarget[i] += mergeSource[i];
+ }
+ }
+
+ private void incrementBatchCountAndMaybeLogStats() {
+ mCurrentBatchCount++;
+ if (mCurrentBatchCount >= LOG_BATCH_FREQUENCY) {
+ logMetricCounts();
+ }
}
/**
diff --git a/core/java/android/app/jank/JankTracker.java b/core/java/android/app/jank/JankTracker.java
index 202281f98c97..469521668d25 100644
--- a/core/java/android/app/jank/JankTracker.java
+++ b/core/java/android/app/jank/JankTracker.java
@@ -89,7 +89,12 @@ public class JankTracker {
* stats
*/
public void mergeAppJankStats(AppJankStats appJankStats) {
- mJankDataProcessor.mergeJankStats(appJankStats, mActivityName);
+ getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ mJankDataProcessor.mergeJankStats(appJankStats, mActivityName);
+ }
+ });
}
public void setActivityName(@NonNull String activityName) {
diff --git a/core/java/android/app/jank/StateTracker.java b/core/java/android/app/jank/StateTracker.java
index c86d5a5cff20..21bb5e8280ee 100644
--- a/core/java/android/app/jank/StateTracker.java
+++ b/core/java/android/app/jank/StateTracker.java
@@ -180,7 +180,11 @@ public class StateTracker {
}
}
- private String getStateKey(String widgetCategory, String widgetId, String widgetState) {
+ /**
+ * Returns a concatenated string of the inputs. This key can be used to retrieve both pending
+ * stats and the state that was used to create the pending stat.
+ */
+ public String getStateKey(String widgetCategory, String widgetId, String widgetState) {
return widgetCategory + widgetId + widgetState;
}
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index 2e3d5e15e037..8b6840c1b552 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -277,6 +277,13 @@ flag {
}
flag {
+ name: "nm_binder_perf_permission_check"
+ namespace: "systemui"
+ description: "Use PermissionManager for areNotificationsEnabled() instead of NMS"
+ bug: "362981561"
+}
+
+flag {
name: "no_sbnholder"
namespace: "systemui"
description: "removes sbnholder from NLS"
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 6086f2455a31..19cd2e69cfdf 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -18,6 +18,7 @@ package android.content;
import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
import static android.content.flags.Flags.FLAG_ENABLE_BIND_PACKAGE_ISOLATED_PROCESS;
+import static android.security.Flags.FLAG_SECURE_LOCKDOWN;
import android.annotation.AttrRes;
import android.annotation.CallbackExecutor;
@@ -4256,6 +4257,7 @@ public abstract class Context {
FINGERPRINT_SERVICE,
//@hide: FACE_SERVICE,
BIOMETRIC_SERVICE,
+ AUTHENTICATION_POLICY_SERVICE,
MEDIA_ROUTER_SERVICE,
TELEPHONY_SERVICE,
TELEPHONY_SUBSCRIPTION_SERVICE,
@@ -4437,6 +4439,9 @@ public abstract class Context {
* web domain approval state.
* <dt> {@link #DISPLAY_HASH_SERVICE} ("display_hash")
* <dd> A {@link android.view.displayhash.DisplayHashManager} for management of display hashes.
+ * <dt> {@link #AUTHENTICATION_POLICY_SERVICE} ("authentication_policy")
+ * <dd> A {@link android.security.authenticationpolicy.AuthenticationPolicyManager}
+ * for managing authentication related policies on the device.
* </dl>
*
* <p>Note: System services obtained via this API may be closely associated with
@@ -4521,8 +4526,9 @@ public abstract class Context {
* @see android.content.pm.verify.domain.DomainVerificationManager
* @see #DISPLAY_HASH_SERVICE
* @see android.view.displayhash.DisplayHashManager
+ * @see #AUTHENTICATION_POLICY_SERVICE
+ * @see android.security.authenticationpolicy.AuthenticationPolicyManager
*/
- // TODO(b/347269120): Re-add @Nullable
public abstract Object getSystemService(@ServiceName @NonNull String name);
/**
@@ -4543,7 +4549,8 @@ public abstract class Context {
* {@link android.os.BatteryManager}, {@link android.app.job.JobScheduler},
* {@link android.app.usage.NetworkStatsManager},
* {@link android.content.pm.verify.domain.DomainVerificationManager},
- * {@link android.view.displayhash.DisplayHashManager}.
+ * {@link android.view.displayhash.DisplayHashManager}
+ * {@link android.security.authenticationpolicy.AuthenticationPolicyManager}.
* </p>
*
* <p>
@@ -4568,7 +4575,6 @@ public abstract class Context {
*/
@SuppressWarnings("unchecked")
@RavenwoodKeep
- // TODO(b/347269120): Re-add @Nullable
public final <T> T getSystemService(@NonNull Class<T> serviceClass) {
// Because subclasses may override getSystemService(String) we cannot
// perform a lookup by class alone. We must first map the class to its
@@ -5183,6 +5189,18 @@ public abstract class Context {
public static final String AUTH_SERVICE = "auth";
/**
+ * Use with {@link #getSystemService(String)} to retrieve an {@link
+ * android.security.authenticationpolicy.AuthenticationPolicyManager}.
+ * @see #getSystemService
+ * @see android.security.authenticationpolicy.AuthenticationPolicyManager
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(FLAG_SECURE_LOCKDOWN)
+ public static final String AUTHENTICATION_POLICY_SERVICE = "authentication_policy";
+
+ /**
* Use with {@link #getSystemService(String)} to retrieve a
* {@link android.hardware.fingerprint.FingerprintManager} for handling management
* of fingerprints.
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 23d17cb5ce50..413eb9886392 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -960,7 +960,6 @@ public class ContextWrapper extends Context {
}
@Override
- // TODO(b/347269120): Re-add @Nullable
public Object getSystemService(String name) {
return mBase.getSystemService(name);
}
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index ce52825ddb73..b10f5e4fe66c 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1320,23 +1320,23 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
264301586L; // buganizer id
/**
- * Excludes the packages the override is applied to from the camera compatibility treatment
- * in free-form windowing mode for fixed-orientation apps.
+ * Includes the packages the override is applied to in the camera compatibility treatment in
+ * free-form windowing mode for fixed-orientation apps.
*
* <p>In free-form windowing mode, the compatibility treatment emulates running on a portrait
* device by letterboxing the app window and changing the camera characteristics to what apps
* commonly expect in a portrait device: 90 and 270 degree sensor rotation for back and front
* cameras, respectively, and setting display rotation to 0.
*
- * <p>Use this flag to disable the compatibility treatment for apps that do not respond well to
- * the treatment.
+ * <p>Use this flag to enable the compatibility treatment for apps in which camera doesn't work
+ * well in freeform windowing.
*
* @hide
*/
@ChangeId
@Overridable
@Disabled
- public static final long OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT =
+ public static final long OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT =
314961188L;
/**
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 37295ac94823..5b305b466dd6 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3200,6 +3200,16 @@ public abstract class PackageManager {
/**
* Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device is capable of ranging with
+ * other devices using channel sounding via Bluetooth Low Energy radio.
+ */
+ @FlaggedApi(com.android.ranging.flags.Flags.FLAG_RANGING_CS_ENABLED)
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_BLUETOOTH_LE_CHANNEL_SOUNDING =
+ "android.hardware.bluetooth_le.channel_sounding";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device has a camera facing away
* from the screen.
*/
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 4285b0a2b91a..8243d88e6260 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -20,6 +20,7 @@ import android.Manifest;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.RequiresPermission;
+import android.health.connect.HealthPermissions;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Printer;
@@ -361,8 +362,10 @@ public class ServiceInfo extends ComponentInfo
* {@link android.Manifest.permission#FOREGROUND_SERVICE_HEALTH} and one of the following
* permissions:
* {@link android.Manifest.permission#ACTIVITY_RECOGNITION},
- * {@link android.Manifest.permission#BODY_SENSORS},
* {@link android.Manifest.permission#HIGH_SAMPLING_RATE_SENSORS}.
+ * {@link android.health.connect.HealthPermissions#READ_HEART_RATE},
+ * {@link android.health.connect.HealthPermissions#READ_SKIN_TEMPERATURE},
+ * {@link android.health.connect.HealthPermissions#READ_OXYGEN_SATURATION},
*/
@RequiresPermission(
allOf = {
@@ -370,10 +373,13 @@ public class ServiceInfo extends ComponentInfo
},
anyOf = {
Manifest.permission.ACTIVITY_RECOGNITION,
- Manifest.permission.BODY_SENSORS,
Manifest.permission.HIGH_SAMPLING_RATE_SENSORS,
+ HealthPermissions.READ_HEART_RATE,
+ HealthPermissions.READ_SKIN_TEMPERATURE,
+ HealthPermissions.READ_OXYGEN_SATURATION,
}
)
+ @FlaggedApi(android.permission.flags.Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED)
public static final int FOREGROUND_SERVICE_TYPE_HEALTH = 1 << 8;
/**
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
index 908999b64961..68b5d782bfbf 100644
--- a/core/java/android/content/res/ApkAssets.java
+++ b/core/java/android/content/res/ApkAssets.java
@@ -124,13 +124,11 @@ public final class ApkAssets {
@Nullable
@GuardedBy("this")
- private StringBlock mStringBlock; // null or closed if mNativePtr = 0.
+ private final StringBlock mStringBlock; // null or closed if mNativePtr = 0.
@PropertyFlags
private final int mFlags;
- private final boolean mIsOverlay;
-
@Nullable
private final AssetsProvider mAssets;
@@ -304,43 +302,40 @@ public final class ApkAssets {
private ApkAssets(@FormatType int format, @NonNull String path, @PropertyFlags int flags,
@Nullable AssetsProvider assets) throws IOException {
- this(format, flags, assets);
Objects.requireNonNull(path, "path");
+ mFlags = flags;
mNativePtr = nativeLoad(format, path, flags, assets);
mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
+ mAssets = assets;
}
private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd,
@NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider assets)
throws IOException {
- this(format, flags, assets);
Objects.requireNonNull(fd, "fd");
Objects.requireNonNull(friendlyName, "friendlyName");
+ mFlags = flags;
mNativePtr = nativeLoadFd(format, fd, friendlyName, flags, assets);
mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
+ mAssets = assets;
}
private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd,
@NonNull String friendlyName, long offset, long length, @PropertyFlags int flags,
@Nullable AssetsProvider assets) throws IOException {
- this(format, flags, assets);
Objects.requireNonNull(fd, "fd");
Objects.requireNonNull(friendlyName, "friendlyName");
+ mFlags = flags;
mNativePtr = nativeLoadFdOffsets(format, fd, friendlyName, offset, length, flags, assets);
mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
+ mAssets = assets;
}
private ApkAssets(@PropertyFlags int flags, @Nullable AssetsProvider assets) {
- this(FORMAT_APK, flags, assets);
+ mFlags = flags;
mNativePtr = nativeLoadEmpty(flags, assets);
mStringBlock = null;
- }
-
- private ApkAssets(@FormatType int format, @PropertyFlags int flags,
- @Nullable AssetsProvider assets) {
- mFlags = flags;
mAssets = assets;
- mIsOverlay = format == FORMAT_IDMAP;
}
@UnsupportedAppUsage
@@ -430,18 +425,6 @@ public final class ApkAssets {
}
}
- public boolean isSystem() {
- return (mFlags & PROPERTY_SYSTEM) != 0;
- }
-
- public boolean isSharedLib() {
- return (mFlags & PROPERTY_DYNAMIC) != 0;
- }
-
- public boolean isOverlay() {
- return mIsOverlay;
- }
-
@Override
public String toString() {
return "ApkAssets{path=" + getDebugName() + "}";
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index bcaceb24d767..e6b93427f413 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -203,25 +203,9 @@ public class ResourcesImpl {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics,
@Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) {
- // Don't reuse assets by default as we have no control over whether they're already
- // inside some other ResourcesImpl.
- this(assets, metrics, config, displayAdjustments, false);
- }
-
- public ResourcesImpl(@NonNull ResourcesImpl orig) {
- // We know for sure that the other assets are in use, so can't reuse the object here.
- this(orig.getAssets(), orig.getMetrics(), orig.getConfiguration(),
- orig.getDisplayAdjustments(), false);
- }
-
- public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics,
- @Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments,
- boolean reuseAssets) {
- final var assetsAndHash =
- ResourcesManager.getInstance().updateResourceImplAssetsWithRegisteredLibs(assets,
- reuseAssets);
- mAssets = assetsAndHash.first;
- mAppliedSharedLibsHash = assetsAndHash.second;
+ mAssets = assets;
+ mAppliedSharedLibsHash =
+ ResourcesManager.getInstance().updateResourceImplWithRegisteredLibs(this);
mMetrics.setToDefaults();
mDisplayAdjustments = displayAdjustments;
mConfiguration.setToDefaults();
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index f23c193e2da0..6fc7d90a8237 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -105,3 +105,12 @@ flag {
# This flag is used to control aapt2 behavior.
is_fixed_read_only: true
}
+
+flag {
+ name: "resources_minor_version_support"
+ is_exported: true
+ namespace: "resource_manager"
+ description: "Feature flag for supporting minor version in Resources"
+ bug: "373535266"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/hardware/contexthub/HubEndpointInfo.java b/core/java/android/hardware/contexthub/HubEndpointInfo.java
index b1d55239ac43..5265d5663459 100644
--- a/core/java/android/hardware/contexthub/HubEndpointInfo.java
+++ b/core/java/android/hardware/contexthub/HubEndpointInfo.java
@@ -156,7 +156,7 @@ public final class HubEndpointInfo implements Parcelable {
mRequiredPermissions = Arrays.asList(endpointInfo.requiredPermissions);
mHubServiceInfos = new ArrayList<>(endpointInfo.services.length);
for (int i = 0; i < endpointInfo.services.length; i++) {
- mHubServiceInfos.set(i, new HubServiceInfo(endpointInfo.services[i]));
+ mHubServiceInfos.add(new HubServiceInfo(endpointInfo.services[i]));
}
}
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 3284761eb273..ed510e467f82 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -281,4 +281,6 @@ interface IInputManager {
AidlInputGestureData[] getCustomInputGestures(int userId, int tag);
AidlInputGestureData[] getAppLaunchBookmarks();
+
+ void resetLockedModifierState();
}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index f8241925dff0..10224c1be788 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -260,7 +260,7 @@ public final class InputManager {
}
/**
- * Custom input gesture error: Input gesture already exists
+ * Custom input gesture result success
*
* @hide
*/
@@ -1590,6 +1590,21 @@ public final class InputManager {
}
/**
+ * Resets locked modifier state (i.e.. Caps Lock, Num Lock, Scroll Lock state)
+ *
+ * @hide
+ */
+ @TestApi
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+ public void resetLockedModifierState() {
+ try {
+ mIm.resetLockedModifierState();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* A callback used to be notified about battery state changes for an input device. The
* {@link #onBatteryStateChanged(int, long, BatteryState)} method will be called once after the
* listener is successfully registered to provide the initial battery state of the device.
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index dadb5c386b76..977c5bd927cf 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -407,6 +407,12 @@ public class InputMethodService extends AbstractInputMethodService {
private boolean mUsingCtrlShiftShortcut = false;
/**
+ * Last handwriting bounds used for stylus handwriting
+ * {@link #setStylusHandwritingRegion(Region)}.
+ */
+ private Region mLastHandwritingRegion;
+
+ /**
* Returns whether {@link InputMethodService} is responsible for rendering the back button and
* the IME switcher button or not when the gestural navigation is enabled.
*
@@ -1532,6 +1538,7 @@ public class InputMethodService extends AbstractInputMethodService {
return;
}
editorInfo.makeCompatible(getApplicationInfo().targetSdkVersion);
+ mLastHandwritingRegion = null;
getInputMethodInternal().restartInput(new RemoteInputConnection(ric, sessionId),
editorInfo);
}
@@ -2840,6 +2847,7 @@ public class InputMethodService extends AbstractInputMethodService {
mHandler.removeCallbacks(mFinishHwRunnable);
}
mFinishHwRunnable = null;
+ mLastHandwritingRegion = null;
final int requestId = mHandwritingRequestId.getAsInt();
mHandwritingRequestId = OptionalInt.empty();
@@ -3166,6 +3174,40 @@ public class InputMethodService extends AbstractInputMethodService {
registerDefaultOnBackInvokedCallback();
}
+ /**
+ * Sets a new stylus handwriting region as user continues to write on an editor on screen.
+ * Stylus strokes that are started within the {@code touchableRegion} are treated as
+ * continuation of handwriting and all the events outside are passed-through to the IME target
+ * app, causing stylus handwriting to finish {@link #finishStylusHandwriting()}.
+ * By default, {@link WindowManager#getMaximumWindowMetrics()} is handwritable and
+ * {@code touchableRegion} resets after each handwriting session.
+ * <p>
+ * For example, the IME can use this API to dynamically expand the stylus handwriting region on
+ * every stylus stroke as user continues to write on an editor. The region should grow around
+ * the last stroke so that a UI element below the IME window is still interactable when it is
+ * spaced sufficiently away (~2 character dimensions) from last stroke.
+ * </p>
+ * <p>
+ * Note: Setting handwriting touchable region is supported on IMEs that support stylus
+ * handwriting {@link InputMethodInfo#supportsStylusHandwriting()}.
+ * </p>
+ *
+ * @param handwritingRegion new stylus handwritable {@link Region} that can accept stylus touch.
+ */
+ @FlaggedApi(Flags.FLAG_ADAPTIVE_HANDWRITING_BOUNDS)
+ public final void setStylusHandwritingRegion(@NonNull Region handwritingRegion) {
+ if (handwritingRegion.equals(mLastHandwritingRegion)) {
+ Log.v(TAG, "Failed to set setStylusHandwritingRegion():"
+ + " same region set twice.");
+ return;
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "Setting new handwriting region for stylus handwriting "
+ + handwritingRegion + " from last " + mLastHandwritingRegion);
+ }
+ mLastHandwritingRegion = handwritingRegion;
+ }
/**
* Registers an {@link OnBackInvokedCallback} to handle back invocation when ahead-of-time
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index 3b5a99ed089a..01222cdd38b3 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -36,6 +36,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@@ -651,28 +652,39 @@ public final class BinderProxy implements IBinder {
private native boolean unlinkToDeathNative(DeathRecipient recipient, int flags);
/**
- * This list is to hold strong reference to the frozen state callbacks. The callbacks are only
- * weakly referenced by JNI so the strong references here are needed to keep the callbacks
- * around until the proxy is GC'ed.
+ * This map is to hold strong reference to the frozen state callbacks.
+ *
+ * The callbacks are only weakly referenced by JNI so the strong references here are needed to
+ * keep the callbacks around until the proxy is GC'ed.
+ *
+ * The key is the original callback passed into {@link #addFrozenStateChangeCallback}. The value
+ * is the wrapped callback created in {@link #addFrozenStateChangeCallback} to dispatch the
+ * calls on the desired executor.
*/
- private List<FrozenStateChangeCallback> mFrozenStateChangeCallbacks =
- Collections.synchronizedList(new ArrayList<>());
+ private Map<FrozenStateChangeCallback, FrozenStateChangeCallback> mFrozenStateChangeCallbacks =
+ Collections.synchronizedMap(new HashMap<>());
/**
* See {@link IBinder#addFrozenStateChangeCallback(FrozenStateChangeCallback)}
*/
- public void addFrozenStateChangeCallback(FrozenStateChangeCallback callback)
+ public void addFrozenStateChangeCallback(Executor executor, FrozenStateChangeCallback callback)
throws RemoteException {
- addFrozenStateChangeCallbackNative(callback);
- mFrozenStateChangeCallbacks.add(callback);
+ FrozenStateChangeCallback wrappedCallback = (who, state) ->
+ executor.execute(() -> callback.onFrozenStateChanged(who, state));
+ addFrozenStateChangeCallbackNative(wrappedCallback);
+ mFrozenStateChangeCallbacks.put(callback, wrappedCallback);
}
/**
* See {@link IBinder#removeFrozenStateChangeCallback}
*/
- public boolean removeFrozenStateChangeCallback(FrozenStateChangeCallback callback) {
- mFrozenStateChangeCallbacks.remove(callback);
- return removeFrozenStateChangeCallbackNative(callback);
+ public boolean removeFrozenStateChangeCallback(FrozenStateChangeCallback callback)
+ throws IllegalArgumentException {
+ FrozenStateChangeCallback wrappedCallback = mFrozenStateChangeCallbacks.remove(callback);
+ if (wrappedCallback == null) {
+ throw new IllegalArgumentException("callback not found");
+ }
+ return removeFrozenStateChangeCallbackNative(wrappedCallback);
}
private native void addFrozenStateChangeCallbackNative(FrozenStateChangeCallback callback)
diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
index 036ccd84a600..4c9f08d80d4b 100644
--- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java
+++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
@@ -123,11 +123,6 @@ public final class MessageQueue {
// We can lift this restriction in the future after we've made it possible for test authors
// to test Looper and MessageQueue without resorting to reflection.
- // Holdback study.
- if (mUseConcurrent && Flags.messageQueueForceLegacy()) {
- mUseConcurrent = false;
- }
-
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java
index a997f4c86704..8cfd32449537 100644
--- a/core/java/android/os/IBinder.java
+++ b/core/java/android/os/IBinder.java
@@ -16,6 +16,7 @@
package android.os;
+import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -25,6 +26,7 @@ import android.compat.annotation.UnsupportedAppUsage;
import java.io.FileDescriptor;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
/**
* Base interface for a remotable object, the core part of a lightweight
@@ -397,12 +399,31 @@ public interface IBinder {
@interface State {
}
+ /**
+ * Represents the frozen state of the remote process.
+ *
+ * While in this state, the remote process won't be able to receive and handle a
+ * transaction. Therefore, any asynchronous transactions will be buffered and delivered when
+ * the process is unfrozen, and any synchronous transactions will result in an error.
+ *
+ * Buffered transactions may be stale by the time that the process is unfrozen and handles
+ * them. To avoid overwhelming the remote process with stale events or overflowing their
+ * buffers, it's best to avoid sending binder transactions to a frozen process.
+ */
int STATE_FROZEN = 0;
+
+ /**
+ * Represents the unfrozen state of the remote process.
+ *
+ * In this state, the process hosting the object can execute and is not restricted
+ * by the freezer from using the CPU or responding to binder transactions.
+ */
int STATE_UNFROZEN = 1;
/**
* Interface for receiving a callback when the process hosting an IBinder
* has changed its frozen state.
+ *
* @param who The IBinder whose hosting process has changed state.
* @param state The latest state.
*/
@@ -427,16 +448,32 @@ public interface IBinder {
* <p>You will only receive state change notifications for remote binders, as local binders by
* definition can't be frozen without you being frozen too.</p>
*
+ * @param executor The executor on which to run the callback.
+ * @param callback The callback used to deliver state change notifications.
+ *
* <p>@throws {@link UnsupportedOperationException} if the kernel binder driver does not support
* this feature.
*/
@FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK)
- default void addFrozenStateChangeCallback(@NonNull FrozenStateChangeCallback callback)
+ default void addFrozenStateChangeCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull FrozenStateChangeCallback callback)
throws RemoteException {
throw new UnsupportedOperationException();
}
/**
+ * Same as {@link #addFrozenStateChangeCallback(Executor, FrozenStateChangeCallback)} except
+ * that callbacks are invoked on a binder thread.
+ *
+ * @hide
+ */
+ default void addFrozenStateChangeCallback(@NonNull FrozenStateChangeCallback callback)
+ throws RemoteException {
+ addFrozenStateChangeCallback(Runnable::run, callback);
+ }
+
+ /**
* Unregister a {@link FrozenStateChangeCallback}. The callback will no longer be invoked when
* the hosting process changes its frozen state.
*/
diff --git a/core/java/android/os/PerformanceHintManager.java b/core/java/android/os/PerformanceHintManager.java
index 224b10d0eaca..2b0042d653b1 100644
--- a/core/java/android/os/PerformanceHintManager.java
+++ b/core/java/android/os/PerformanceHintManager.java
@@ -22,6 +22,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemService;
import android.annotation.TestApi;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import com.android.internal.util.Preconditions;
@@ -106,7 +107,9 @@ public final class PerformanceHintManager {
* All timings should be in {@link SystemClock#uptimeNanos()}.
*/
public static class Session implements Closeable {
- private long mNativeSessionPtr;
+ /** @hide */
+ @UnsupportedAppUsage
+ public long mNativeSessionPtr;
/** @hide */
public Session(long nativeSessionPtr) {
diff --git a/core/java/android/os/RemoteCallbackList.java b/core/java/android/os/RemoteCallbackList.java
index 91c482faf7d7..d5630fd46eb4 100644
--- a/core/java/android/os/RemoteCallbackList.java
+++ b/core/java/android/os/RemoteCallbackList.java
@@ -16,6 +16,7 @@
package android.os;
+import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -29,6 +30,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Executor;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
@@ -134,6 +136,7 @@ public class RemoteCallbackList<E extends IInterface> {
private final @FrozenCalleePolicy int mFrozenCalleePolicy;
private final int mMaxQueueSize;
+ private final Executor mExecutor;
private final class Interface implements IBinder.DeathRecipient,
IBinder.FrozenStateChangeCallback {
@@ -197,7 +200,7 @@ public class RemoteCallbackList<E extends IInterface> {
void maybeSubscribeToFrozenCallback() throws RemoteException {
if (mFrozenCalleePolicy != FROZEN_CALLEE_POLICY_UNSET) {
try {
- mBinder.addFrozenStateChangeCallback(this);
+ mBinder.addFrozenStateChangeCallback(mExecutor, this);
} catch (UnsupportedOperationException e) {
// The kernel does not support frozen notifications. In this case we want to
// silently fall back to FROZEN_CALLEE_POLICY_UNSET. This is done by simply
@@ -237,6 +240,7 @@ public class RemoteCallbackList<E extends IInterface> {
private @FrozenCalleePolicy int mFrozenCalleePolicy;
private int mMaxQueueSize = DEFAULT_MAX_QUEUE_SIZE;
private InterfaceDiedCallback mInterfaceDiedCallback;
+ private Executor mExecutor;
/**
* Creates a Builder for {@link RemoteCallbackList}.
@@ -285,6 +289,18 @@ public class RemoteCallbackList<E extends IInterface> {
}
/**
+ * Sets the executor to be used when invoking callbacks asynchronously.
+ *
+ * This is only used when callbacks need to be invoked asynchronously, e.g. when the process
+ * hosting a callback becomes unfrozen. Callbacks that can be invoked immediately run on the
+ * same thread that calls {@link #broadcast} synchronously.
+ */
+ public @NonNull Builder setExecutor(@NonNull @CallbackExecutor Executor executor) {
+ mExecutor = executor;
+ return this;
+ }
+
+ /**
* For notifying when the process hosting a callback interface has died.
*
* @param <E> The remote callback interface type.
@@ -308,15 +324,21 @@ public class RemoteCallbackList<E extends IInterface> {
* @return The built {@link RemoteCallbackList} object.
*/
public @NonNull RemoteCallbackList<E> build() {
+ Executor executor = mExecutor;
+ if (executor == null && mFrozenCalleePolicy != FROZEN_CALLEE_POLICY_UNSET) {
+ // TODO Throw an exception here once the existing API caller is updated to provide
+ // an executor.
+ executor = new HandlerExecutor(Handler.getMain());
+ }
if (mInterfaceDiedCallback != null) {
- return new RemoteCallbackList<E>(mFrozenCalleePolicy, mMaxQueueSize) {
+ return new RemoteCallbackList<E>(mFrozenCalleePolicy, mMaxQueueSize, executor) {
@Override
public void onCallbackDied(E deadInterface, Object cookie) {
mInterfaceDiedCallback.onInterfaceDied(this, deadInterface, cookie);
}
};
}
- return new RemoteCallbackList<E>(mFrozenCalleePolicy, mMaxQueueSize);
+ return new RemoteCallbackList<E>(mFrozenCalleePolicy, mMaxQueueSize, executor);
}
}
@@ -341,13 +363,23 @@ public class RemoteCallbackList<E extends IInterface> {
}
/**
+ * Returns the executor used when invoking callbacks asynchronously.
+ *
+ * @return The executor.
+ */
+ @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK)
+ public @Nullable Executor getExecutor() {
+ return mExecutor;
+ }
+
+ /**
* Creates a RemoteCallbackList with {@link #FROZEN_CALLEE_POLICY_UNSET}. This is equivalent to
* <pre>
* new RemoteCallbackList.Build(RemoteCallbackList.FROZEN_CALLEE_POLICY_UNSET).build()
* </pre>
*/
public RemoteCallbackList() {
- this(FROZEN_CALLEE_POLICY_UNSET, DEFAULT_MAX_QUEUE_SIZE);
+ this(FROZEN_CALLEE_POLICY_UNSET, DEFAULT_MAX_QUEUE_SIZE, null);
}
/**
@@ -362,10 +394,14 @@ public class RemoteCallbackList<E extends IInterface> {
* recipient's process is frozen. Once the limit is reached, the oldest callbacks would be
* dropped to keep the size under limit. Ignored except for
* {@link #FROZEN_CALLEE_POLICY_ENQUEUE_ALL}.
+ *
+ * @param executor The executor used when invoking callbacks asynchronously.
*/
- private RemoteCallbackList(@FrozenCalleePolicy int frozenCalleePolicy, int maxQueueSize) {
+ private RemoteCallbackList(@FrozenCalleePolicy int frozenCalleePolicy, int maxQueueSize,
+ @CallbackExecutor Executor executor) {
mFrozenCalleePolicy = frozenCalleePolicy;
mMaxQueueSize = maxQueueSize;
+ mExecutor = executor;
}
/**
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 9b1bf057b815..2ef8764a5221 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -4,15 +4,6 @@ container: "system"
# keep-sorted start block=yes newline_separated=yes
flag {
- # Holdback study for concurrent MessageQueue.
- # Do not promote beyond trunkfood.
- namespace: "system_performance"
- name: "message_queue_force_legacy"
- description: "Whether to holdback concurrent MessageQueue (force legacy)."
- bug: "336880969"
-}
-
-flag {
name: "adpf_gpu_report_actual_work_duration"
is_exported: true
namespace: "game"
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index b5a822b715d5..1093503a45a5 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -418,3 +418,12 @@ flag {
description: "Add new checkOp APIs that accept attributionTag"
bug: "240617242"
}
+
+flag {
+ name: "device_policy_management_role_split_create_managed_profile_enabled"
+ is_fixed_read_only: true
+ is_exported: true
+ namespace: "enterprise"
+ description: "Gives the device policy management role the ability to create a managed profile using new APIs"
+ bug: "375382324"
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index a35c9c1cd4ec..19b0c6fcdd77 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -20454,6 +20454,29 @@ public final class Settings {
* @hide
*/
public static final String AUTO_BEDTIME_MODE = "auto_bedtime_mode";
+
+ /**
+ * Indicates that all elements of the system status tray on wear should be rendered
+ * by default wear system.
+ *
+ * @hide
+ */
+ public static final int STATUS_TRAY_CONFIGURATION_DEFAULT = 0;
+
+ /**
+ * Indicates that all elements of the system status tray on wear should be hidden.
+ *
+ * @hide
+ */
+ public static final int STATUS_TRAY_CONFIGURATION_SYSTEM_HIDDEN = 1;
+
+ /**
+ * Configuration of system status tray in wear.
+ *
+ * @hide
+ */
+ public static final String WEAR_SYSTEM_STATUS_TRAY_CONFIGURATION =
+ "wear_system_status_tray_configuration";
}
}
diff --git a/core/java/android/security/authenticationpolicy/AuthenticationPolicyManager.java b/core/java/android/security/authenticationpolicy/AuthenticationPolicyManager.java
new file mode 100644
index 000000000000..75abd5fa4bb0
--- /dev/null
+++ b/core/java/android/security/authenticationpolicy/AuthenticationPolicyManager.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2024 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.security.authenticationpolicy;
+
+import static android.Manifest.permission.MANAGE_SECURE_LOCK_DEVICE;
+import static android.security.Flags.FLAG_SECURE_LOCKDOWN;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.RemoteException;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * AuthenticationPolicyManager is a centralized interface for managing authentication related
+ * policies on the device. This includes device locking capabilities to protect users in "at risk"
+ * environments.
+ *
+ * AuthenticationPolicyManager is designed to protect Android users by integrating with apps and
+ * key system components, such as the lock screen. It is not related to enterprise control surfaces
+ * and does not offer additional administrative controls.
+ *
+ * <p>
+ * To use this class, call {@link #enableSecureLockDevice} to enable secure lock on the device.
+ * This will require the caller to have the
+ * {@link android.Manifest.permission#MANAGE_SECURE_LOCK_DEVICE} permission.
+ *
+ * <p>
+ * To disable secure lock on the device, call {@link #disableSecureLockDevice}. This will require
+ * the caller to have the {@link android.Manifest.permission#MANAGE_SECURE_LOCK_DEVICE} permission.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_SECURE_LOCKDOWN)
+@SystemService(Context.AUTHENTICATION_POLICY_SERVICE)
+public final class AuthenticationPolicyManager {
+ private static final String TAG = "AuthenticationPolicyManager";
+
+ @NonNull private final IAuthenticationPolicyService mAuthenticationPolicyService;
+ @NonNull private final Context mContext;
+
+ /**
+ * Error result code for {@link #enableSecureLockDevice} and {@link
+ * #disableSecureLockDevice}.
+ *
+ * Secure lock device request status unknown.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(FLAG_SECURE_LOCKDOWN)
+ public static final int ERROR_UNKNOWN = 0;
+
+ /**
+ * Success result code for {@link #enableSecureLockDevice} and {@link #disableSecureLockDevice}.
+ *
+ * Secure lock device request successful.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(FLAG_SECURE_LOCKDOWN)
+ public static final int SUCCESS = 1;
+
+ /**
+ * Error result code for {@link #enableSecureLockDevice} and {@link #disableSecureLockDevice}.
+ *
+ * Secure lock device is unsupported.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(FLAG_SECURE_LOCKDOWN)
+ public static final int ERROR_UNSUPPORTED = 2;
+
+
+ /**
+ * Error result code for {@link #enableSecureLockDevice} and {@link #disableSecureLockDevice}.
+ *
+ * Invalid secure lock device request params provided.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(FLAG_SECURE_LOCKDOWN)
+ public static final int ERROR_INVALID_PARAMS = 3;
+
+
+ /**
+ * Error result code for {@link #enableSecureLockDevice} and {@link #disableSecureLockDevice}.
+ *
+ * Secure lock device is unavailable because there are no biometrics enrolled on the device.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(FLAG_SECURE_LOCKDOWN)
+ public static final int ERROR_NO_BIOMETRICS_ENROLLED = 4;
+
+ /**
+ * Error result code for {@link #enableSecureLockDevice} and {@link #disableSecureLockDevice}.
+ *
+ * Secure lock device is unavailable because the device has no biometric hardware or the
+ * biometric sensors do not meet
+ * {@link android.hardware.biometrics.BiometricManager.Authenticators#BIOMETRIC_STRONG}
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(FLAG_SECURE_LOCKDOWN)
+ public static final int ERROR_INSUFFICIENT_BIOMETRICS = 5;
+
+ /**
+ * Error result code for {@link #enableSecureLockDevice}.
+ *
+ * Secure lock is already enabled.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(FLAG_SECURE_LOCKDOWN)
+ public static final int ERROR_ALREADY_ENABLED = 6;
+
+ /**
+ * Communicates the current status of a request to enable secure lock on the device.
+ *
+ * @hide
+ */
+ @IntDef(prefix = {"ENABLE_SECURE_LOCK_DEVICE_STATUS_"}, value = {
+ ERROR_UNKNOWN,
+ SUCCESS,
+ ERROR_UNSUPPORTED,
+ ERROR_INVALID_PARAMS,
+ ERROR_NO_BIOMETRICS_ENROLLED,
+ ERROR_INSUFFICIENT_BIOMETRICS,
+ ERROR_ALREADY_ENABLED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EnableSecureLockDeviceRequestStatus {}
+
+ /**
+ * Communicates the current status of a request to disable secure lock on the device.
+ *
+ * @hide
+ */
+ @IntDef(prefix = {"DISABLE_SECURE_LOCK_DEVICE_STATUS_"}, value = {
+ ERROR_UNKNOWN,
+ SUCCESS,
+ ERROR_UNSUPPORTED,
+ ERROR_INVALID_PARAMS,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DisableSecureLockDeviceRequestStatus {}
+
+ /** @hide */
+ public AuthenticationPolicyManager(@NonNull Context context,
+ @NonNull IAuthenticationPolicyService authenticationPolicyService) {
+ mContext = context;
+ mAuthenticationPolicyService = authenticationPolicyService;
+ }
+
+ /**
+ * Called by a privileged component to remotely enable secure lock on the device.
+ *
+ * Secure lock is an enhanced security state that restricts access to sensitive data (app
+ * notifications, widgets, quick settings, assistant, etc) and requires multi-factor
+ * authentication for device entry, such as
+ * {@link android.hardware.biometrics.BiometricManager.Authenticators#DEVICE_CREDENTIAL} and
+ * {@link android.hardware.biometrics.BiometricManager.Authenticators#BIOMETRIC_STRONG}.
+ *
+ * If secure lock is already enabled when this method is called, it will return
+ * {@link ERROR_ALREADY_ENABLED}.
+ *
+ * @param params EnableSecureLockDeviceParams for caller to supply params related to the secure
+ * lock device request
+ * @return @EnableSecureLockDeviceRequestStatus int indicating the result of the secure lock
+ * device request
+ *
+ * @hide
+ */
+ @EnableSecureLockDeviceRequestStatus
+ @RequiresPermission(MANAGE_SECURE_LOCK_DEVICE)
+ @SystemApi
+ @FlaggedApi(FLAG_SECURE_LOCKDOWN)
+ public int enableSecureLockDevice(@NonNull EnableSecureLockDeviceParams params) {
+ try {
+ return mAuthenticationPolicyService.enableSecureLockDevice(params);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Called by a privileged component to disable secure lock on the device.
+ *
+ * If secure lock is already disabled when this method is called, it will return
+ * {@link SUCCESS}.
+ *
+ * @param params @DisableSecureLockDeviceParams for caller to supply params related to the
+ * secure lock device request
+ * @return @DisableSecureLockDeviceRequestStatus int indicating the result of the secure lock
+ * device request
+ *
+ * @hide
+ */
+ @DisableSecureLockDeviceRequestStatus
+ @RequiresPermission(MANAGE_SECURE_LOCK_DEVICE)
+ @SystemApi
+ @FlaggedApi(FLAG_SECURE_LOCKDOWN)
+ public int disableSecureLockDevice(@NonNull DisableSecureLockDeviceParams params) {
+ try {
+ return mAuthenticationPolicyService.disableSecureLockDevice(params);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/core/java/android/security/authenticationpolicy/DisableSecureLockDeviceParams.aidl b/core/java/android/security/authenticationpolicy/DisableSecureLockDeviceParams.aidl
new file mode 100644
index 000000000000..81f7726a500c
--- /dev/null
+++ b/core/java/android/security/authenticationpolicy/DisableSecureLockDeviceParams.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 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.security.authenticationpolicy;
+
+/**
+ * @hide
+ */
+parcelable DisableSecureLockDeviceParams; \ No newline at end of file
diff --git a/core/java/android/security/authenticationpolicy/DisableSecureLockDeviceParams.java b/core/java/android/security/authenticationpolicy/DisableSecureLockDeviceParams.java
new file mode 100644
index 000000000000..64a3f0f60f96
--- /dev/null
+++ b/core/java/android/security/authenticationpolicy/DisableSecureLockDeviceParams.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2024 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.security.authenticationpolicy;
+
+import static android.security.Flags.FLAG_SECURE_LOCKDOWN;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Parameters related to a request to disable secure lock on the device.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_SECURE_LOCKDOWN)
+public final class DisableSecureLockDeviceParams implements Parcelable {
+
+ /**
+ * Client message associated with the request to disable secure lock on the device. This message
+ * will be shown on the device when secure lock mode is disabled.
+ */
+ private final @NonNull String mMessage;
+
+ /**
+ * Creates DisableSecureLockDeviceParams with the given params.
+ *
+ * @param message Allows clients to pass in a message with information about the request to
+ * disable secure lock on the device. This message will be shown to the user when
+ * secure lock mode is disabled. If an empty string is provided, it will default
+ * to a system-defined string (e.g. "Secure lock mode has been disabled.")
+ */
+ public DisableSecureLockDeviceParams(@NonNull String message) {
+ mMessage = message;
+ }
+
+ private DisableSecureLockDeviceParams(@NonNull Parcel in) {
+ mMessage = Objects.requireNonNull(in.readString8());
+ }
+
+ public static final @NonNull Creator<DisableSecureLockDeviceParams> CREATOR =
+ new Creator<DisableSecureLockDeviceParams>() {
+ @Override
+ public DisableSecureLockDeviceParams createFromParcel(Parcel in) {
+ return new DisableSecureLockDeviceParams(in);
+ }
+
+ @Override
+ public DisableSecureLockDeviceParams[] newArray(int size) {
+ return new DisableSecureLockDeviceParams[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString8(mMessage);
+ }
+}
diff --git a/core/java/android/security/authenticationpolicy/EnableSecureLockDeviceParams.aidl b/core/java/android/security/authenticationpolicy/EnableSecureLockDeviceParams.aidl
new file mode 100644
index 000000000000..9e496f82ec69
--- /dev/null
+++ b/core/java/android/security/authenticationpolicy/EnableSecureLockDeviceParams.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 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.security.authenticationpolicy;
+
+/**
+ * @hide
+ */
+parcelable EnableSecureLockDeviceParams; \ No newline at end of file
diff --git a/core/java/android/security/authenticationpolicy/EnableSecureLockDeviceParams.java b/core/java/android/security/authenticationpolicy/EnableSecureLockDeviceParams.java
new file mode 100644
index 000000000000..1d727727ce37
--- /dev/null
+++ b/core/java/android/security/authenticationpolicy/EnableSecureLockDeviceParams.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2024 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.security.authenticationpolicy;
+
+import static android.security.Flags.FLAG_SECURE_LOCKDOWN;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+
+/**
+ * Parameters related to a request to enable secure lock on the device.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_SECURE_LOCKDOWN)
+public final class EnableSecureLockDeviceParams implements Parcelable {
+
+ /**
+ * Client message associated with the request to enable secure lock on the device. This message
+ * will be shown on the device when secure lock mode is enabled.
+ */
+ private final @NonNull String mMessage;
+
+ /**
+ * Creates EnableSecureLockDeviceParams with the given params.
+ *
+ * @param message Allows clients to pass in a message with information about the request to
+ * enable secure lock on the device. This message will be shown to the user when
+ * secure lock mode is enabled. If an empty string is provided, it will default
+ * to a system-defined string (e.g. "Device is securely locked remotely.")
+ */
+ public EnableSecureLockDeviceParams(@NonNull String message) {
+ mMessage = message;
+ }
+
+ private EnableSecureLockDeviceParams(@NonNull Parcel in) {
+ mMessage = Objects.requireNonNull(in.readString8());
+ }
+
+ public static final @NonNull Creator<EnableSecureLockDeviceParams> CREATOR =
+ new Creator<EnableSecureLockDeviceParams>() {
+ @Override
+ public EnableSecureLockDeviceParams createFromParcel(Parcel in) {
+ return new EnableSecureLockDeviceParams(in);
+ }
+
+ @Override
+ public EnableSecureLockDeviceParams[] newArray(int size) {
+ return new EnableSecureLockDeviceParams[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString8(mMessage);
+ }
+}
diff --git a/core/java/android/security/authenticationpolicy/IAuthenticationPolicyService.aidl b/core/java/android/security/authenticationpolicy/IAuthenticationPolicyService.aidl
new file mode 100644
index 000000000000..5ad4534c257a
--- /dev/null
+++ b/core/java/android/security/authenticationpolicy/IAuthenticationPolicyService.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 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.security.authenticationpolicy;
+
+import android.security.authenticationpolicy.EnableSecureLockDeviceParams;
+import android.security.authenticationpolicy.DisableSecureLockDeviceParams;
+
+/**
+ * Communication channel from AuthenticationPolicyManager to AuthenticationPolicyService.
+ * @hide
+ */
+interface IAuthenticationPolicyService {
+ @EnforcePermission("MANAGE_SECURE_LOCK_DEVICE")
+ int enableSecureLockDevice(in EnableSecureLockDeviceParams params);
+
+ @EnforcePermission("MANAGE_SECURE_LOCK_DEVICE")
+ int disableSecureLockDevice(in DisableSecureLockDeviceParams params);
+} \ No newline at end of file
diff --git a/core/java/android/security/authenticationpolicy/OWNERS b/core/java/android/security/authenticationpolicy/OWNERS
new file mode 100644
index 000000000000..4310d1a3a9db
--- /dev/null
+++ b/core/java/android/security/authenticationpolicy/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/security/authenticationpolicy/OWNERS \ No newline at end of file
diff --git a/core/java/android/service/carrier/CarrierMessagingService.java b/core/java/android/service/carrier/CarrierMessagingService.java
index 61213e6293ba..a825a7e110f5 100644
--- a/core/java/android/service/carrier/CarrierMessagingService.java
+++ b/core/java/android/service/carrier/CarrierMessagingService.java
@@ -16,6 +16,7 @@
package android.service.carrier;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -26,6 +27,8 @@ import android.net.Uri;
import android.os.IBinder;
import android.os.RemoteException;
+import com.android.internal.telephony.flags.Flags;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
@@ -97,16 +100,317 @@ public abstract class CarrierMessagingService extends Service {
public static final int SEND_STATUS_RETRY_ON_CARRIER_NETWORK = 1;
/**
- * SMS/MMS sending failed. We should not retry via the carrier network.
+ * SMS/MMS sending failed due to an unspecified issue. Sending will not be retried via the
+ * carrier network.
+ *
+ * <p>Maps to SmsManager.RESULT_RIL_GENERIC_FAILURE for SMS and SmsManager.MMS_ERROR_UNSPECIFIED
+ * for MMS.
*/
public static final int SEND_STATUS_ERROR = 2;
+ /**
+ * More precise error reasons for outbound SMS send requests. These will not be retried on the
+ * carrier network.
+ *
+ * <p>Each code maps directly to an SmsManager code (e.g. SEND_STATS_RESULT_ERROR_NULL_PDU maps
+ * to SmsManager.RESULT_ERROR_NULL_PDU).
+ */
+
+ /**
+ * Generic failure cause.
+ *
+ * @see android.telephony.SmsManager.RESULT_ERROR_GENERIC_FAILURE
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int SEND_STATUS_RESULT_ERROR_GENERIC_FAILURE = 200;
+
+ /**
+ * Failed because no pdu provided.
+ *
+ * @see android.telephony.SmsManager.RESULT_ERROR_NULL_PDU
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int SEND_STATUS_RESULT_ERROR_NULL_PDU = 201;
+
+ /**
+ * Failed because service is currently unavailable.
+ *
+ * @see android.telephony.SmsManager.RESULT_ERROR_NO_SERVICE
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int SEND_STATUS_RESULT_ERROR_NO_SERVICE = 202;
+
+ /**
+ * Failed because we reached the sending queue limit.
+ *
+ * @see android.telephony.SmsManager.RESULT_ERROR_LIMIT_EXCEEDED
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int SEND_STATUS_RESULT_ERROR_LIMIT_EXCEEDED = 203;
+
+ /**
+ * Failed because FDN is enabled.
+ *
+ * @see android.telephony.SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int SEND_STATUS_RESULT_ERROR_FDN_CHECK_FAILURE = 204;
+
+ /**
+ * Failed because user denied the sending of this short code.
+ *
+ * @see android.telephony.SmsManager.RESULT_ERROR_SHORT_CODE_NOT_ALLOWED
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int SEND_STATUS_RESULT_ERROR_SHORT_CODE_NOT_ALLOWED = 205;
+
+ /**
+ * Failed because the user has denied this app ever send premium short codes.
+ *
+ * @see android.telephony.SmsManager.RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int SEND_STATUS_RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED = 206;
+
+ /**
+ * Failed because of network rejection.
+ *
+ * @see android.telephony.SmsManager.RESULT_NETWORK_REJECT
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int SEND_STATUS_RESULT_NETWORK_REJECT = 207;
+
+ /**
+ * Failed because of invalid arguments.
+ *
+ * @see android.telephony.SmsManager.RESULT_INVALID_ARGUMENTS
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int SEND_STATUS_RESULT_INVALID_ARGUMENTS = 208;
+
+ /**
+ * Failed because of an invalid state.
+ *
+ * @see android.telephony.SmsManager.RESULT_INVALID_STATE
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int SEND_STATUS_RESULT_INVALID_STATE = 209;
+
+ /**
+ * Failed because the sms format is not valid.
+ *
+ * @see android.telephony.SmsManager.RESULT_INVALID_SMS_FORMAT
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int SEND_STATUS_RESULT_INVALID_SMS_FORMAT = 210;
+
+ /**
+ * Failed because of a network error.
+ *
+ * @see android.telephony.SmsManager.RESULT_NETWORK_ERROR
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int SEND_STATUS_RESULT_NETWORK_ERROR = 211;
+
+ /**
+ * Failed because of an encoding error.
+ *
+ * @see android.telephony.SmsManager.RESULT_ENCODING_ERROR
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int SEND_STATUS_RESULT_ENCODING_ERROR = 212;
+
+ /**
+ * Failed because of an invalid smsc address
+ *
+ * @see android.telephony.SmsManager.RESULT_INVALID_SMSC_ADDRESS
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int SEND_STATUS_RESULT_INVALID_SMSC_ADDRESS = 213;
+
+ /**
+ * Failed because the operation is not allowed.
+ *
+ * @see android.telephony.SmsManager.RESULT_OPERATION_NOT_ALLOWED
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int SEND_STATUS_RESULT_OPERATION_NOT_ALLOWED = 214;
+
+ /**
+ * Failed because the operation was cancelled.
+ *
+ * @see android.telephony.SmsManager.RESULT_CANCELLED
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int SEND_STATUS_RESULT_CANCELLED = 215;
+
+ /**
+ * Failed because the request is not supported.
+ *
+ * @see android.telephony.SmsManager.RESULT_REQUEST_NOT_SUPPORTED
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int SEND_STATUS_RESULT_REQUEST_NOT_SUPPORTED = 216;
+
+ /**
+ * Failed sending during an emergency call.
+ *
+ * @see android.telephony.SmsManager.RESULT_SMS_BLOCKED_DURING_EMERGENCY
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int SEND_STATUS_RESULT_SMS_BLOCKED_DURING_EMERGENCY = 217;
+
+ /**
+ * Failed to send an sms retry.
+ *
+ * @see android.telephony.SmsManager.RESULT_SMS_SEND_RETRY_FAILED
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int SEND_STATUS_RESULT_SMS_SEND_RETRY_FAILED = 218;
+
+ /**
+ * More precise error reasons for outbound MMS send requests. These will not be retried on the
+ * carrier network.
+ *
+ * <p>Each code maps directly to an SmsManager code (e.g.
+ * SEND_STATUS_MMS_ERROR_UNABLE_CONNECT_MMS maps to SmsManager.MMS_ERROR_UNABLE_CONNECT_MMS).
+ */
+
+ /**
+ * Unspecific MMS error occurred during send.
+ *
+ * @see android.telephony.SmsManager.MMS_ERROR_UNSPECIFIED
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int SEND_STATUS_MMS_ERROR_UNSPECIFIED = 400;
+
+ /**
+ * ApnException occurred during MMS network setup.
+ *
+ * @see android.telephony.SmsManager.MMS_ERROR_INVALID_APN
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int SEND_STATUS_MMS_ERROR_INVALID_APN = 401;
+
+ /**
+ * An error occurred during the MMS connection setup.
+ *
+ * @see android.telephony.SmsManager.MMS_ERROR_UNABLE_CONNECT_MMS
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int SEND_STATUS_MMS_ERROR_UNABLE_CONNECT_MMS = 402;
+
+ /**
+ * An error occurred during the HTTP client setup.
+ *
+ * @see android.telephony.SmsManager.MMS_ERROR_HTTP_FAILURE
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int SEND_STATUS_MMS_ERROR_HTTP_FAILURE = 403;
+
+ /**
+ * An I/O error occurred reading the PDU.
+ *
+ * @see android.telephony.SmsManager.MMS_ERROR_IO_ERROR
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int SEND_STATUS_MMS_ERROR_IO_ERROR = 404;
+
+ /**
+ * An error occurred while retrying sending the MMS.
+ *
+ * @see android.telephony.SmsManager.MMS_ERROR_RETRY
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int SEND_STATUS_MMS_ERROR_RETRY = 405;
+
+ /**
+ * The carrier-dependent configuration values could not be loaded.
+ *
+ * @see android.telephony.SmsManager.MMS_ERROR_CONFIGURATION_ERROR
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int SEND_STATUS_MMS_ERROR_CONFIGURATION_ERROR = 406;
+
+ /**
+ * There is neither Wi-Fi nor mobile data network.
+ *
+ * @see android.telephony.SmsManager.MMS_ERROR_NO_DATA_NETWORK
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int SEND_STATUS_MMS_ERROR_NO_DATA_NETWORK = 407;
+
+ /**
+ * The subscription id for the send is invalid.
+ *
+ * @see android.telephony.SmsManager.MMS_ERROR_INVALID_SUBSCRIPTION_ID
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int SEND_STATUS_MMS_ERROR_INVALID_SUBSCRIPTION_ID = 408;
+
+ /**
+ * The subscription id for the send is inactive.
+ *
+ * @see android.telephony.SmsManager.MMS_ERROR_INACTIVE_SUBSCRIPTION
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int SEND_STATUS_MMS_ERROR_INACTIVE_SUBSCRIPTION = 409;
+
+ /**
+ * Data is disabled for the MMS APN.
+ *
+ * @see android.telephony.SmsManager.MMS_ERROR_DATA_DISABLED
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int SEND_STATUS_MMS_ERROR_DATA_DISABLED = 410;
+
+ /**
+ * MMS is disabled by a carrier.
+ *
+ * @see android.telephony.SmsManager.MMS_ERROR_MMS_DISABLED_BY_CARRIER
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int SEND_STATUS_MMS_ERROR_MMS_DISABLED_BY_CARRIER = 411;
+
/** @hide */
- @IntDef(prefix = { "SEND_STATUS_" }, value = {
- SEND_STATUS_OK,
- SEND_STATUS_RETRY_ON_CARRIER_NETWORK,
- SEND_STATUS_ERROR
- })
+ @IntDef(
+ prefix = {"SEND_STATUS_"},
+ value = {
+ SEND_STATUS_OK,
+ SEND_STATUS_RETRY_ON_CARRIER_NETWORK,
+ SEND_STATUS_ERROR,
+ SEND_STATUS_RESULT_ERROR_GENERIC_FAILURE,
+ SEND_STATUS_RESULT_ERROR_NULL_PDU,
+ SEND_STATUS_RESULT_ERROR_NO_SERVICE,
+ SEND_STATUS_RESULT_ERROR_LIMIT_EXCEEDED,
+ SEND_STATUS_RESULT_ERROR_FDN_CHECK_FAILURE,
+ SEND_STATUS_RESULT_ERROR_SHORT_CODE_NOT_ALLOWED,
+ SEND_STATUS_RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED,
+ SEND_STATUS_RESULT_NETWORK_REJECT,
+ SEND_STATUS_RESULT_INVALID_ARGUMENTS,
+ SEND_STATUS_RESULT_INVALID_STATE,
+ SEND_STATUS_RESULT_INVALID_SMS_FORMAT,
+ SEND_STATUS_RESULT_NETWORK_ERROR,
+ SEND_STATUS_RESULT_ENCODING_ERROR,
+ SEND_STATUS_RESULT_INVALID_SMSC_ADDRESS,
+ SEND_STATUS_RESULT_OPERATION_NOT_ALLOWED,
+ SEND_STATUS_RESULT_CANCELLED,
+ SEND_STATUS_RESULT_REQUEST_NOT_SUPPORTED,
+ SEND_STATUS_RESULT_SMS_BLOCKED_DURING_EMERGENCY,
+ SEND_STATUS_RESULT_SMS_SEND_RETRY_FAILED,
+ SEND_STATUS_MMS_ERROR_UNSPECIFIED,
+ SEND_STATUS_MMS_ERROR_INVALID_APN,
+ SEND_STATUS_MMS_ERROR_UNABLE_CONNECT_MMS,
+ SEND_STATUS_MMS_ERROR_HTTP_FAILURE,
+ SEND_STATUS_MMS_ERROR_IO_ERROR,
+ SEND_STATUS_MMS_ERROR_RETRY,
+ SEND_STATUS_MMS_ERROR_CONFIGURATION_ERROR,
+ SEND_STATUS_MMS_ERROR_NO_DATA_NETWORK,
+ SEND_STATUS_MMS_ERROR_INVALID_SUBSCRIPTION_ID,
+ SEND_STATUS_MMS_ERROR_INACTIVE_SUBSCRIPTION,
+ SEND_STATUS_MMS_ERROR_DATA_DISABLED,
+ SEND_STATUS_MMS_ERROR_MMS_DISABLED_BY_CARRIER
+ })
@Retention(RetentionPolicy.SOURCE)
public @interface SendResult {}
@@ -121,16 +425,138 @@ public abstract class CarrierMessagingService extends Service {
public static final int DOWNLOAD_STATUS_RETRY_ON_CARRIER_NETWORK = 1;
/**
- * MMS downloading failed. We should not retry via the carrier network.
+ * MMS downloading failed due to an unspecified issue. Downloading will not be retried via the
+ * carrier network.
+ *
+ * <p>Maps to SmsManager.MMR_ERROR_UNSPECIFIED.
*/
public static final int DOWNLOAD_STATUS_ERROR = 2;
+ /**
+ * More precise error reasons for inbound MMS download requests. These will not be retried on
+ * the carrier network.
+ *
+ * <p>Each code maps directly to an SmsManager code (e.g.
+ * DOWNLOAD_STATUS_MMS_ERROR_UNABLE_CONNECT_MMS maps to
+ * SmsManager.MMS_ERROR_UNABLE_CONNECT_MMS).
+ */
+
+ /**
+ * Unspecific MMS error occurred during download.
+ *
+ * @see android.telephony.SmsManager.MMS_ERROR_UNSPECIFIED
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int DOWNLOAD_STATUS_MMS_ERROR_UNSPECIFIED = 600;
+
+ /**
+ * ApnException occurred during MMS network setup.
+ *
+ * @see android.telephony.SmsManager.MMS_ERROR_INVALID_APN
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int DOWNLOAD_STATUS_MMS_ERROR_INVALID_APN = 601;
+
+ /**
+ * An error occurred during the MMS connection setup.
+ *
+ * @see android.telephony.SmsManager.MMS_ERROR_UNABLE_CONNECT_MMS
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int DOWNLOAD_STATUS_MMS_ERROR_UNABLE_CONNECT_MMS = 602;
+
+ /**
+ * An error occurred during the HTTP client setup.
+ *
+ * @see android.telephony.SmsManager.MMS_ERROR_HTTP_FAILURE
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int DOWNLOAD_STATUS_MMS_ERROR_HTTP_FAILURE = 603;
+
+ /**
+ * An I/O error occurred reading the PDU.
+ *
+ * @see android.telephony.SmsManager.MMS_ERROR_IO_ERROR
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int DOWNLOAD_STATUS_MMS_ERROR_IO_ERROR = 604;
+
+ /**
+ * An error occurred while retrying downloading the MMS.
+ *
+ * @see android.telephony.SmsManager.MMS_ERROR_RETRY
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int DOWNLOAD_STATUS_MMS_ERROR_RETRY = 605;
+
+ /**
+ * The carrier-dependent configuration values could not be loaded.
+ *
+ * @see android.telephony.SmsManager.MMS_ERROR_CONFIGURATION_ERROR
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int DOWNLOAD_STATUS_MMS_ERROR_CONFIGURATION_ERROR = 606;
+
+ /**
+ * There is neither Wi-Fi nor mobile data network.
+ *
+ * @see android.telephony.SmsManager.MMS_ERROR_NO_DATA_NETWORK
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int DOWNLOAD_STATUS_MMS_ERROR_NO_DATA_NETWORK = 607;
+
+ /**
+ * The subscription id for the download is invalid.
+ *
+ * @see android.telephony.SmsManager.MMS_ERROR_INVALID_SUBSCRIPTION_ID
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int DOWNLOAD_STATUS_MMS_ERROR_INVALID_SUBSCRIPTION_ID = 608;
+
+ /**
+ * The subscription id for the download is inactive.
+ *
+ * @see android.telephony.SmsManager.MMS_ERROR_INACTIVE_SUBSCRIPTION
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int DOWNLOAD_STATUS_MMS_ERROR_INACTIVE_SUBSCRIPTION = 609;
+
+ /**
+ * Data is disabled for the MMS APN.
+ *
+ * @see android.telephony.SmsManager.MMS_ERROR_DATA_DISABLED
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int DOWNLOAD_STATUS_MMS_ERROR_DATA_DISABLED = 610;
+
+ /**
+ * MMS is disabled by a carrier.
+ *
+ * @see android.telephony.SmsManager.MMS_ERROR_MMS_DISABLED_BY_CARRIER
+ */
+ @FlaggedApi(Flags.FLAG_TEMPORARY_FAILURES_IN_CARRIER_MESSAGING_SERVICE)
+ public static final int DOWNLOAD_STATUS_MMS_ERROR_MMS_DISABLED_BY_CARRIER = 611;
+
/** @hide */
- @IntDef(prefix = { "DOWNLOAD_STATUS_" }, value = {
- DOWNLOAD_STATUS_OK,
- DOWNLOAD_STATUS_RETRY_ON_CARRIER_NETWORK,
- DOWNLOAD_STATUS_ERROR
- })
+ @IntDef(
+ prefix = {"DOWNLOAD_STATUS_"},
+ value = {
+ DOWNLOAD_STATUS_OK,
+ DOWNLOAD_STATUS_RETRY_ON_CARRIER_NETWORK,
+ DOWNLOAD_STATUS_ERROR,
+ DOWNLOAD_STATUS_MMS_ERROR_UNSPECIFIED,
+ DOWNLOAD_STATUS_MMS_ERROR_INVALID_APN,
+ DOWNLOAD_STATUS_MMS_ERROR_UNABLE_CONNECT_MMS,
+ DOWNLOAD_STATUS_MMS_ERROR_HTTP_FAILURE,
+ DOWNLOAD_STATUS_MMS_ERROR_IO_ERROR,
+ DOWNLOAD_STATUS_MMS_ERROR_RETRY,
+ DOWNLOAD_STATUS_MMS_ERROR_CONFIGURATION_ERROR,
+ DOWNLOAD_STATUS_MMS_ERROR_NO_DATA_NETWORK,
+ DOWNLOAD_STATUS_MMS_ERROR_INVALID_SUBSCRIPTION_ID,
+ DOWNLOAD_STATUS_MMS_ERROR_INACTIVE_SUBSCRIPTION,
+ DOWNLOAD_STATUS_MMS_ERROR_DATA_DISABLED,
+ DOWNLOAD_STATUS_MMS_ERROR_MMS_DISABLED_BY_CARRIER
+ })
@Retention(RetentionPolicy.SOURCE)
public @interface DownloadResult {}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 24328eb1e825..13887781f1ec 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -2532,7 +2532,7 @@ public class ZenModeConfig implements Parcelable {
/** Returns whether the rule id corresponds to an implicit rule. */
public static boolean isImplicitRuleId(@NonNull String ruleId) {
- return ruleId.startsWith(IMPLICIT_RULE_ID_PREFIX);
+ return ruleId != null && ruleId.startsWith(IMPLICIT_RULE_ID_PREFIX);
}
private static int[] tryParseHourAndMinute(String value) {
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index eebdeadcdeb2..c7d5a9fe6041 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -253,6 +253,9 @@ flag {
namespace: "lse_desktop_experience"
description: "Enables enter desktop windowing transition & motion polish changes"
bug: "369763947"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
@@ -260,6 +263,9 @@ flag {
namespace: "lse_desktop_experience"
description: "Enables exit desktop windowing transition & motion polish changes"
bug: "353650462"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
@@ -343,6 +349,9 @@ flag {
namespace: "lse_desktop_experience"
description: "Enables custom transitions for alt-tab app launches in Desktop Mode."
bug: "370735595"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
@@ -350,6 +359,9 @@ flag {
namespace: "lse_desktop_experience"
description: "Enables custom transitions for app launches in Desktop Mode."
bug: "375992828"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig
index 96b9dc7cab0e..bb4770768cb1 100644
--- a/core/java/android/window/flags/window_surfaces.aconfig
+++ b/core/java/android/window/flags/window_surfaces.aconfig
@@ -29,14 +29,6 @@ flag {
flag {
namespace: "window_surfaces"
- name: "secure_window_state"
- description: "Move SC secure flag to WindowState level"
- is_fixed_read_only: true
- bug: "308662081"
-}
-
-flag {
- namespace: "window_surfaces"
name: "trusted_presentation_listener_for_window"
is_exported: true
description: "Enable trustedPresentationListener on windows public API"
diff --git a/core/jni/android_os_PerformanceHintManager.cpp b/core/jni/android_os_PerformanceHintManager.cpp
index aebe7ea7ee61..0f78c9e93a00 100644
--- a/core/jni/android_os_PerformanceHintManager.cpp
+++ b/core/jni/android_os_PerformanceHintManager.cpp
@@ -88,9 +88,10 @@ void ensureAPerformanceHintBindingInitialized() {
"Failed to find required symbol "
"APerformanceHint_getPreferredUpdateRateNanos!");
- gAPH_createSessionFn = (APH_createSession)dlsym(handle_, "APerformanceHint_createSession");
+ gAPH_createSessionFn =
+ (APH_createSession)dlsym(handle_, "APerformanceHint_createSessionFromJava");
LOG_ALWAYS_FATAL_IF(gAPH_createSessionFn == nullptr,
- "Failed to find required symbol APerformanceHint_createSession!");
+ "Failed to find required symbol APerformanceHint_createSessionFromJava!");
gAPH_updateTargetWorkDurationFn =
(APH_updateTargetWorkDuration)dlsym(handle_,
@@ -106,9 +107,9 @@ void ensureAPerformanceHintBindingInitialized() {
"Failed to find required symbol "
"APerformanceHint_reportActualWorkDuration!");
- gAPH_closeSessionFn = (APH_closeSession)dlsym(handle_, "APerformanceHint_closeSession");
+ gAPH_closeSessionFn = (APH_closeSession)dlsym(handle_, "APerformanceHint_closeSessionFromJava");
LOG_ALWAYS_FATAL_IF(gAPH_closeSessionFn == nullptr,
- "Failed to find required symbol APerformanceHint_closeSession!");
+ "Failed to find required symbol APerformanceHint_closeSessionFromJava!");
gAPH_sendHintFn = (APH_sendHint)dlsym(handle_, "APerformanceHint_sendHint");
LOG_ALWAYS_FATAL_IF(gAPH_sendHintFn == nullptr,
diff --git a/core/res/Android.bp b/core/res/Android.bp
index 0e4e22b09e24..8042b30df4dc 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -161,6 +161,7 @@ android_app {
"android.app.flags-aconfig",
"android.appwidget.flags-aconfig",
"android.content.pm.flags-aconfig",
+ "android.media.audio-aconfig",
"android.provider.flags-aconfig",
"camera_platform_flags",
"android.net.platform.flags-aconfig",
@@ -170,6 +171,7 @@ android_app {
"android.os.vibrator.flags-aconfig",
"android.media.tv.flags-aconfig",
"android.security.flags-aconfig",
+ "device_policy_aconfig_flags",
"com.android.hardware.input.input-aconfig",
"aconfig_trade_in_mode_flags",
"art-aconfig-flags",
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 7fcbf19d137f..9b2d3384d99c 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2649,6 +2649,22 @@
android:label="@string/permlab_getAccounts" />
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
+ <!-- @SystemApi Allows access to remove an account.
+ @FlaggedApi(android.app.admin.flags.Flags.FLAG_SPLIT_CREATE_MANAGED_PROFILE_ENABLED)
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.REMOVE_ACCOUNTS"
+ android:protectionLevel="signature|role"
+ android:featureFlag="android.app.admin.flags.split_create_managed_profile_enabled" />
+
+ <!-- @SystemApi Allows access to copy an account to another user.
+ @FlaggedApi(android.app.admin.flags.Flags.FLAG_SPLIT_CREATE_MANAGED_PROFILE_ENABLED)
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.COPY_ACCOUNTS"
+ android:protectionLevel="signature|role"
+ android:featureFlag="android.app.admin.flags.split_create_managed_profile_enabled" />
+
<!-- Allows applications to call into AccountAuthenticators.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.ACCOUNT_MANAGER"
@@ -6492,6 +6508,15 @@
<permission android:name="android.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT"
android:protectionLevel="signature|privileged|role" />
+ <!-- @SystemApi Allows an application to bypass concurrency restrictions while
+ recording audio. For example, apps with this permission can continue to record
+ while a voice call is active.</p>
+ @FlaggedApi(android.media.audio.Flags.FLAG_CONCURRENT_AUDIO_RECORD_BYPASS_PERMISSION)
+ @hide -->
+ <permission android:name="android.permission.BYPASS_CONCURRENT_RECORD_AUDIO_RESTRICTION"
+ android:featureFlag="android.media.audio.concurrent_audio_record_bypass_permission"
+ android:protectionLevel="signature|privileged|role" />
+
<!-- @SystemApi Allows an application to capture audio for hotword detection.
<p>Not for use by third-party applications.</p>
@hide -->
@@ -8651,6 +8676,17 @@
<permission android:name="android.permission.SETUP_FSVERITY"
android:protectionLevel="signature|privileged"/>
+ <!-- @SystemApi
+ @FlaggedApi(android.security.Flags.FLAG_SECURE_LOCKDOWN)
+ Allows an application to lock down the device into an enhanced security state.
+ <p>Not for use by third-party applications.
+ <p>Protection level: signature|privileged
+ @hide
+ -->
+ <permission android:name="android.permission.MANAGE_SECURE_LOCK_DEVICE"
+ android:protectionLevel="signature|privileged"
+ android:featureFlag="android.security.secure_lockdown" />
+
<!-- Allows app to enter trade-in-mode.
<p>Protection level: signature|privileged
@hide
diff --git a/core/res/res/layout/notification_2025_template_heads_up_base.xml b/core/res/res/layout/notification_2025_template_heads_up_base.xml
new file mode 100644
index 000000000000..e4ff835a3524
--- /dev/null
+++ b/core/res/res/layout/notification_2025_template_heads_up_base.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2014 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/status_bar_latest_event_content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:clipChildren="false"
+ android:tag="headsUp"
+ >
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:orientation="vertical"
+ >
+
+ <include
+ layout="@layout/notification_2025_template_collapsed_base"
+ android:id="@null"
+ />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="-20dp"
+ android:clipChildren="false"
+ android:orientation="vertical"
+ >
+
+ <ViewStub
+ android:layout="@layout/notification_material_reply_text"
+ android:id="@+id/notification_material_reply_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ />
+
+ <include
+ layout="@layout/notification_template_smart_reply_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+ android:layout_marginEnd="@dimen/notification_content_margin_end"
+ android:layout_marginTop="@dimen/notification_content_margin"
+ />
+
+ <include layout="@layout/notification_material_action_list" />
+ </LinearLayout>
+ </LinearLayout>
+</FrameLayout>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a2a19a23d431..5a6b66cc1504 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2390,6 +2390,7 @@
<java-symbol type="layout" name="notification_material_action_list" />
<java-symbol type="layout" name="notification_material_action_tombstone" />
<java-symbol type="layout" name="notification_2025_template_collapsed_base" />
+ <java-symbol type="layout" name="notification_2025_template_heads_up_base" />
<java-symbol type="layout" name="notification_2025_template_header" />
<java-symbol type="layout" name="notification_template_material_base" />
<java-symbol type="layout" name="notification_template_material_heads_up_base" />
diff --git a/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/src/com/android/frameworks/coretests/bfscctestapp/BfsccTestAppCmdService.java b/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/src/com/android/frameworks/coretests/bfscctestapp/BfsccTestAppCmdService.java
index fe54aa8d87f0..945147db1ef5 100644
--- a/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/src/com/android/frameworks/coretests/bfscctestapp/BfsccTestAppCmdService.java
+++ b/core/tests/coretests/BinderFrozenStateChangeCallbackTestApp/src/com/android/frameworks/coretests/bfscctestapp/BfsccTestAppCmdService.java
@@ -18,6 +18,8 @@ package com.android.frameworks.coretests.bfscctestapp;
import android.app.Service;
import android.content.Intent;
+import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.RemoteException;
@@ -36,6 +38,7 @@ public class BfsccTestAppCmdService extends Service {
@Override
public void listenTo(IBinder binder) throws RemoteException {
binder.addFrozenStateChangeCallback(
+ new HandlerExecutor(Handler.getMain()),
(IBinder who, int state) -> mNotifications.offer(state));
}
diff --git a/core/tests/coretests/src/android/os/BinderFrozenStateChangeNotificationTest.java b/core/tests/coretests/src/android/os/BinderFrozenStateChangeNotificationTest.java
index 195a18a5f521..523fe1a8aa5d 100644
--- a/core/tests/coretests/src/android/os/BinderFrozenStateChangeNotificationTest.java
+++ b/core/tests/coretests/src/android/os/BinderFrozenStateChangeNotificationTest.java
@@ -200,7 +200,7 @@ public class BinderFrozenStateChangeNotificationTest {
IBinder.FrozenStateChangeCallback callback =
(IBinder who, int state) -> results.offer(who);
try {
- binder.addFrozenStateChangeCallback(callback);
+ binder.addFrozenStateChangeCallback(new HandlerExecutor(Handler.getMain()), callback);
} catch (UnsupportedOperationException e) {
return;
}
@@ -227,7 +227,7 @@ public class BinderFrozenStateChangeNotificationTest {
final IBinder.FrozenStateChangeCallback callback =
(IBinder who, int state) ->
queue.offer(state == IBinder.FrozenStateChangeCallback.STATE_FROZEN);
- binder.addFrozenStateChangeCallback(callback);
+ binder.addFrozenStateChangeCallback(new HandlerExecutor(Handler.getMain()), callback);
return callback;
} catch (UnsupportedOperationException e) {
return null;
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java b/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java
index 67de25eede42..75118873dd75 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java
@@ -42,6 +42,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.FileDescriptor;
+import java.util.concurrent.Executor;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -125,7 +126,7 @@ public class BinderDeathDispatcherTest {
}
@Override
- public void addFrozenStateChangeCallback(FrozenStateChangeCallback callback)
+ public void addFrozenStateChangeCallback(Executor e, FrozenStateChangeCallback callback)
throws RemoteException {
}
diff --git a/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java b/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java
index 75aca1b8820c..7ce2ed823540 100644
--- a/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java
+++ b/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java
@@ -23,10 +23,11 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import android.platform.test.ravenwood.RavenwoodConfig;
+import android.platform.test.ravenwood.RavenwoodRule;
import androidx.test.filters.SmallTest;
+import org.junit.Rule;
import org.junit.Test;
import java.util.Objects;
@@ -39,8 +40,8 @@ public class SystemPropertiesTest {
private static final String PERSIST_KEY = "persist.sys.testkey";
private static final String NONEXIST_KEY = "doesnotexist_2341431";
- @RavenwoodConfig.Config
- public static final RavenwoodConfig mRavenwood = new RavenwoodConfig.Builder()
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
.setSystemPropertyMutable(KEY, null)
.setSystemPropertyMutable(UNSET_KEY, null)
.setSystemPropertyMutable(PERSIST_KEY, null)
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index fea7cb4b422c..329e5de15f5e 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -533,6 +533,8 @@ applications that come with the platform
<permission name="com.android.cellbroadcastservice.FULL_ACCESS_CELL_BROADCAST_HISTORY" />
<!-- Permission required for ATS test - CarDevicePolicyManagerTest -->
<permission name="android.permission.LOCK_DEVICE" />
+ <!-- Permission required for AuthenticationPolicyManagerTest -->
+ <permission name="android.permission.MANAGE_SECURE_LOCK_DEVICE" />
<!-- Permissions required for CTS test - CtsSafetyCenterTestCases -->
<permission name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
<permission name="android.permission.READ_SAFETY_CENTER_STATUS" />
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml
index 5e41865cd31e..375968ab0ad2 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml
@@ -113,7 +113,7 @@
style="?android:attr/buttonBarButtonStyle"
android:layout_width="41dp"
android:layout_height="@dimen/desktop_mode_maximize_menu_button_height"
- android:layout_marginRight="4dp"
+ android:layout_marginEnd="4dp"
android:background="@drawable/desktop_mode_maximize_menu_button_background"
android:importantForAccessibility="yes"
android:contentDescription="@string/desktop_mode_maximize_menu_snap_left_button_text"
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 7078d66d265c..21ec84d9bc0a 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -504,17 +504,15 @@
<dimen name="desktop_mode_maximize_menu_buttons_fill_radius">4dp</dimen>
<!-- The padding between the outline and fill of the maximize menu snap and maximize buttons. -->
<dimen name="desktop_mode_maximize_menu_snap_and_maximize_buttons_fill_padding">4dp</dimen>
- <!-- The padding between the outline and fill of the maximize menu snap and maximize buttons. -->
- <dimen name="desktop_mode_maximize_menu_snap_and_maximize_buttons_fill_padding_bottom">8dp</dimen>
<!-- The vertical padding between the outline and fill of the maximize menu restore button. -->
<dimen name="desktop_mode_maximize_menu_restore_button_fill_vertical_padding">13dp</dimen>
<!-- The horizontal padding between the outline and fill of the maximize menu restore button. -->
- <dimen name="desktop_mode_maximize_menu_restore_button_fill_horizontal_padding">21dp</dimen>
+ <dimen name="desktop_mode_maximize_menu_restore_button_fill_horizontal_padding">15dp</dimen>
<!-- The padding between the outline and fill of the maximize menu immersive button. -->
- <dimen name="desktop_mode_maximize_menu_immersive_button_fill_padding">4dp</dimen>
+ <dimen name="desktop_mode_maximize_menu_immersive_button_fill_padding">0dp</dimen>
<!-- The corner radius of the maximize menu. -->
- <dimen name="desktop_mode_maximize_menu_corner_radius">8dp</dimen>
+ <dimen name="desktop_mode_maximize_menu_corner_radius">16dp</dimen>
<!-- The radius of the Maximize menu shadow. -->
<dimen name="desktop_mode_maximize_menu_shadow_radius">8dp</dimen>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 012579a6d40c..468c345259d0 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -318,7 +318,7 @@
<!-- Maximize menu maximize button string. -->
<string name="desktop_mode_maximize_menu_maximize_text">Maximize Screen</string>
<!-- Maximize menu snap buttons string. -->
- <string name="desktop_mode_maximize_menu_snap_text">Snap Screen</string>
+ <string name="desktop_mode_maximize_menu_snap_text">Resize</string>
<!-- Snap resizing non-resizable string. -->
<string name="desktop_mode_non_resizable_snap_text">App can\'t be moved here</string>
<!-- Accessibility text for the Maximize Menu's immersive button [CHAR LIMIT=NONE] -->
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index 55cda783005f..597a921302b7 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -29,6 +29,7 @@
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:windowAnimationStyle">@null</item>
+ <item name="android:windowIsTranslucent">true</item>
</style>
<style name="Animation.ForcedResizable" parent="@android:style/Animation">
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 603a9ec6bedb..b82496e45415 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1327,7 +1327,11 @@ public class BubbleController implements ConfigurationChangeListener,
/** Promote the provided bubble from the overflow view. */
public void promoteBubbleFromOverflow(Bubble bubble) {
- mLogger.log(bubble, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_BACK_TO_STACK);
+ if (isShowingAsBubbleBar()) {
+ mLogger.log(bubble, BubbleLogger.Event.BUBBLE_BAR_OVERFLOW_REMOVE_BACK_TO_BAR);
+ } else {
+ mLogger.log(bubble, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_BACK_TO_STACK);
+ }
ProtoLog.d(WM_SHELL_BUBBLES, "promoteBubbleFromOverflow=%s", bubble.getKey());
bubble.setInflateSynchronously(mInflateSynchronously);
bubble.setShouldAutoExpand(true);
@@ -1350,11 +1354,7 @@ public class BubbleController implements ConfigurationChangeListener,
if (BubbleOverflow.KEY.equals(key)) {
mBubbleData.setSelectedBubbleFromLauncher(mBubbleData.getOverflow());
mLayerView.showExpandedView(mBubbleData.getOverflow());
- if (wasExpanded) {
- mLogger.log(BubbleLogger.Event.BUBBLE_BAR_BUBBLE_SWITCHED);
- } else {
- mLogger.log(BubbleLogger.Event.BUBBLE_BAR_EXPANDED);
- }
+ mLogger.log(BubbleLogger.Event.BUBBLE_BAR_OVERFLOW_SELECTED);
return;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 4de9dfa54c5d..294569190f68 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -759,7 +759,9 @@ public class BubbleData {
if (b != null) {
b.stopInflation();
}
- mLogger.logOverflowRemove(b, reason);
+ if (!mPositioner.isShowingInBubbleBar()) {
+ mLogger.logStackOverflowRemove(b, reason);
+ }
mOverflowBubbles.remove(b);
mStateChange.bubbleRemoved(b, reason);
mStateChange.removedOverflowBubble = b;
@@ -802,6 +804,27 @@ public class BubbleData {
setNewSelectedIndex(indexToRemove);
}
maybeSendDeleteIntent(reason, bubbleToRemove);
+
+ if (mPositioner.isShowingInBubbleBar()) {
+ logBubbleBarBubbleRemoved(bubbleToRemove, reason);
+ }
+ }
+
+ private void logBubbleBarBubbleRemoved(Bubble bubble, @DismissReason int reason) {
+ switch (reason) {
+ case Bubbles.DISMISS_NOTIF_CANCEL:
+ mLogger.log(bubble, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_REMOVED_CANCELED);
+ break;
+ case Bubbles.DISMISS_TASK_FINISHED:
+ mLogger.log(bubble, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_ACTIVITY_FINISH);
+ break;
+ case Bubbles.DISMISS_BLOCKED:
+ case Bubbles.DISMISS_NO_LONGER_BUBBLE:
+ mLogger.log(bubble, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_REMOVED_BLOCKED);
+ break;
+ default:
+ // skip logging other events
+ }
}
private void setNewSelectedIndex(int indexOfSelected) {
@@ -862,7 +885,7 @@ public class BubbleData {
return;
}
ProtoLog.d(WM_SHELL_BUBBLES, "overflowBubble=%s", bubble.getKey());
- mLogger.logOverflowAdd(bubble, reason);
+ mLogger.logOverflowAdd(bubble, mPositioner.isShowingInBubbleBar(), reason);
if (mOverflowBubbles.isEmpty()) {
mStateChange.showOverflowChanged = true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java
index 36630733e1da..347df330c4b3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java
@@ -182,10 +182,12 @@ public class BubbleLogger {
}
/**
+ * Log when a bubble is removed from overflow in stack view
+ *
* @param b Bubble removed from overflow
* @param r Reason that bubble was removed
*/
- public void logOverflowRemove(Bubble b, @Bubbles.DismissReason int r) {
+ public void logStackOverflowRemove(Bubble b, @Bubbles.DismissReason int r) {
if (r == Bubbles.DISMISS_NOTIF_CANCEL) {
log(b, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_CANCEL);
} else if (r == Bubbles.DISMISS_GROUP_CANCELLED) {
@@ -201,13 +203,19 @@ public class BubbleLogger {
* @param b Bubble added to overflow
* @param r Reason that bubble was added to overflow
*/
- public void logOverflowAdd(Bubble b, @Bubbles.DismissReason int r) {
- if (r == Bubbles.DISMISS_AGED) {
- log(b, Event.BUBBLE_OVERFLOW_ADD_AGED);
- } else if (r == Bubbles.DISMISS_USER_GESTURE) {
- log(b, Event.BUBBLE_OVERFLOW_ADD_USER_GESTURE);
- } else if (r == Bubbles.DISMISS_RELOAD_FROM_DISK) {
- log(b, Event.BUBBLE_OVERFLOW_RECOVER);
+ public void logOverflowAdd(Bubble b, boolean bubbleBar, @Bubbles.DismissReason int r) {
+ if (bubbleBar) {
+ if (r == Bubbles.DISMISS_AGED) {
+ log(b, Event.BUBBLE_BAR_OVERFLOW_ADD_AGED);
+ }
+ } else {
+ if (r == Bubbles.DISMISS_AGED) {
+ log(b, Event.BUBBLE_OVERFLOW_ADD_AGED);
+ } else if (r == Bubbles.DISMISS_USER_GESTURE) {
+ log(b, Event.BUBBLE_OVERFLOW_ADD_USER_GESTURE);
+ } else if (r == Bubbles.DISMISS_RELOAD_FROM_DISK) {
+ log(b, Event.BUBBLE_OVERFLOW_RECOVER);
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index c386c9398624..068b2d246500 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -830,6 +830,13 @@ public class BubblePositioner {
mShowingInBubbleBar = showingInBubbleBar;
}
+ /**
+ * Whether bubbles ar showing in the bubble bar from launcher.
+ */
+ boolean isShowingInBubbleBar() {
+ return mShowingInBubbleBar;
+ }
+
public void setBubbleBarLocation(BubbleBarLocation location) {
mBubbleBarLocation = location;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index dab30b0f0b96..0f21756bb863 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -88,6 +88,7 @@ import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.splitscreen.StageTaskListener;
import java.io.PrintWriter;
+import java.util.List;
import java.util.function.Consumer;
/**
@@ -139,15 +140,21 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
private final Rect mTempRect = new Rect();
private final Rect mRootBounds = new Rect();
private final Rect mDividerBounds = new Rect();
- // Bounds1 final position should be always at top or left
- private final Rect mBounds1 = new Rect();
- // Bounds2 final position should be always at bottom or right
- private final Rect mBounds2 = new Rect();
+ /**
+ * A list of stage bounds, kept in order from top/left to bottom/right. These are the sizes of
+ * the app surfaces, not necessarily the same as the size of the rendered content.
+ * See {@link #mContentBounds}.
+ */
+ private final List<Rect> mStageBounds = List.of(new Rect(), new Rect());
+ /**
+ * A list of app content bounds, kept in order from top/left to bottom/right. These are the
+ * sizes of the rendered app contents, not necessarily the same as the size of the drawn app
+ * surfaces. See {@link #mStageBounds}.
+ */
+ private final List<Rect> mContentBounds = List.of(new Rect(), new Rect());
// The temp bounds outside of display bounds for side stage when split screen inactive to avoid
// flicker next time active split screen.
private final Rect mInvisibleBounds = new Rect();
- private final Rect mWinBounds1 = new Rect();
- private final Rect mWinBounds2 = new Rect();
private final SplitLayoutHandler mSplitLayoutHandler;
private final SplitWindowManager mSplitWindowManager;
private final DisplayController mDisplayController;
@@ -233,26 +240,26 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
mDividerWindowWidth = mDividerSize + 2 * mDividerInsets;
}
- /** Gets bounds of the primary split with screen based coordinate. */
- public Rect getBounds1() {
- return new Rect(mBounds1);
+ /** Gets the bounds of the top/left app in screen-based coordinates. */
+ public Rect getTopLeftBounds() {
+ return mStageBounds.getFirst();
}
- /** Gets bounds of the primary split with parent based coordinate. */
- public Rect getRefBounds1() {
- Rect outBounds = getBounds1();
- outBounds.offset(-mRootBounds.left, -mRootBounds.top);
- return outBounds;
+ /** Gets the bounds of the bottom/right app in screen-based coordinates. */
+ public Rect getBottomRightBounds() {
+ return mStageBounds.getLast();
}
- /** Gets bounds of the secondary split with screen based coordinate. */
- public Rect getBounds2() {
- return new Rect(mBounds2);
+ /** Gets the bounds of the top/left app in parent-based coordinates. */
+ public Rect getTopLeftRefBounds() {
+ Rect outBounds = getTopLeftBounds();
+ outBounds.offset(-mRootBounds.left, -mRootBounds.top);
+ return outBounds;
}
- /** Gets bounds of the secondary split with parent based coordinate. */
- public Rect getRefBounds2() {
- final Rect outBounds = getBounds2();
+ /** Gets the bounds of the bottom/right app in parent-based coordinates. */
+ public Rect getBottomRightRefBounds() {
+ Rect outBounds = getBottomRightBounds();
outBounds.offset(-mRootBounds.left, -mRootBounds.top);
return outBounds;
}
@@ -274,31 +281,42 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
return outBounds;
}
- /** Gets bounds of the primary split with screen based coordinate on the param Rect. */
- public void getBounds1(Rect rect) {
- rect.set(mBounds1);
+ /** Copies the top/left bounds to the provided Rect (screen-based coordinates). */
+ public void copyTopLeftBounds(Rect rect) {
+ rect.set(getTopLeftBounds());
}
- /** Gets bounds of the primary split with parent based coordinate on the param Rect. */
- public void getRefBounds1(Rect rect) {
- getBounds1(rect);
+ /** Copies the top/left bounds to the provided Rect (parent-based coordinates). */
+ public void copyTopLeftRefBounds(Rect rect) {
+ copyTopLeftBounds(rect);
rect.offset(-mRootBounds.left, -mRootBounds.top);
}
- /** Gets bounds of the secondary split with screen based coordinate on the param Rect. */
- public void getBounds2(Rect rect) {
- rect.set(mBounds2);
+ /** Copies the bottom/right bounds to the provided Rect (screen-based coordinates). */
+ public void copyBottomRightBounds(Rect rect) {
+ rect.set(getBottomRightBounds());
}
- /** Gets bounds of the secondary split with parent based coordinate on the param Rect. */
- public void getRefBounds2(Rect rect) {
- getBounds2(rect);
+ /** Copies the bottom/right bounds to the provided Rect (parent-based coordinates). */
+ public void copyBottomRightRefBounds(Rect rect) {
+ copyBottomRightBounds(rect);
rect.offset(-mRootBounds.left, -mRootBounds.top);
}
- /** Gets root bounds of the whole split layout on the param Rect. */
- public void getRootBounds(Rect rect) {
- rect.set(mRootBounds);
+ /**
+ * Gets the content bounds of the top/left app (the bounds of where the app contents would be
+ * drawn). Might be larger than the available surface space.
+ */
+ public Rect getTopLeftContentBounds() {
+ return mContentBounds.getFirst();
+ }
+
+ /**
+ * Gets the content bounds of the bottom/right app (the bounds of where the app contents would
+ * be drawn). Might be larger than the available surface space.
+ */
+ public Rect getBottomRightContentBounds() {
+ return mContentBounds.getLast();
}
/** Gets bounds of divider window with screen based coordinate on the param Rect. */
@@ -340,8 +358,10 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
*/
public float getDividerPositionAsFraction() {
return Math.min(1f, Math.max(0f, mIsLeftRightSplit
- ? (float) ((mBounds1.right + mBounds2.left) / 2f) / mBounds2.right
- : (float) ((mBounds1.bottom + mBounds2.top) / 2f) / mBounds2.bottom));
+ ? (float) ((getTopLeftBounds().right + getBottomRightBounds().left) / 2f)
+ / getBottomRightBounds().right
+ : (float) ((getTopLeftBounds().bottom + getBottomRightBounds().top) / 2f)
+ / getBottomRightBounds().bottom));
}
private void updateInvisibleRect() {
@@ -435,7 +455,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
}
private void updateBounds(int position) {
- updateBounds(position, mBounds1, mBounds2, mDividerBounds, true /* setEffectBounds */);
+ updateBounds(position, getTopLeftBounds(), getBottomRightBounds(), mDividerBounds,
+ true /* setEffectBounds */);
}
/** Updates recording bounds of divider window and both of the splits. */
@@ -638,8 +659,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
updateBounds(mDividerPosition);
mWinToken1 = null;
mWinToken2 = null;
- mWinBounds1.setEmpty();
- mWinBounds2.setEmpty();
+ getTopLeftContentBounds().setEmpty();
+ getBottomRightContentBounds().setEmpty();
}
/**
@@ -835,7 +856,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
insets.left != 0 || insets.top != 0 || insets.right != 0 || insets.bottom != 0;
final int dividerPos = mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(
- mIsLeftRightSplit ? mBounds2.width() : mBounds2.height()).position;
+ mIsLeftRightSplit ? getBottomRightBounds().width() : getBottomRightBounds().height()
+ ).position;
final Rect endBounds1 = new Rect();
final Rect endBounds2 = new Rect();
final Rect endDividerBounds = new Rect();
@@ -847,12 +869,12 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
endBounds2.offset(-mRootBounds.left, -mRootBounds.top);
endDividerBounds.offset(-mRootBounds.left, -mRootBounds.top);
- ValueAnimator animator1 = moveSurface(t, topLeftStage, getRefBounds1(), endBounds1,
+ ValueAnimator animator1 = moveSurface(t, topLeftStage, getTopLeftRefBounds(), endBounds1,
-insets.left, -insets.top, true /* roundCorners */, true /* isGoingBehind */,
shouldVeil);
- ValueAnimator animator2 = moveSurface(t, bottomRightStage, getRefBounds2(), endBounds2,
- insets.left, insets.top, true /* roundCorners */, false /* isGoingBehind */,
- shouldVeil);
+ ValueAnimator animator2 = moveSurface(t, bottomRightStage, getBottomRightRefBounds(),
+ endBounds2, insets.left, insets.top, true /* roundCorners */,
+ false /* isGoingBehind */, shouldVeil);
ValueAnimator animator3 = moveSurface(t, null /* stage */, getRefDividerBounds(),
endDividerBounds, 0 /* offsetX */, 0 /* offsetY */, false /* roundCorners */,
false /* isGoingBehind */, false /* addVeil */);
@@ -1059,10 +1081,10 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
// Resets layer of divider bar to make sure it is always on top.
t.setLayer(dividerLeash, Integer.MAX_VALUE);
}
- getRefBounds1(mTempRect);
+ copyTopLeftRefBounds(mTempRect);
t.setPosition(leash1, mTempRect.left, mTempRect.top)
.setWindowCrop(leash1, mTempRect.width(), mTempRect.height());
- getRefBounds2(mTempRect);
+ copyBottomRightRefBounds(mTempRect);
t.setPosition(leash2, mTempRect.left, mTempRect.top)
.setWindowCrop(leash2, mTempRect.width(), mTempRect.height());
@@ -1084,15 +1106,17 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
public boolean applyTaskChanges(WindowContainerTransaction wct,
ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2) {
boolean boundsChanged = false;
- if (!mBounds1.equals(mWinBounds1) || !task1.token.equals(mWinToken1)) {
- setTaskBounds(wct, task1, mBounds1);
- mWinBounds1.set(mBounds1);
+ if (!getTopLeftBounds().equals(getTopLeftContentBounds())
+ || !task1.token.equals(mWinToken1)) {
+ setTaskBounds(wct, task1, getTopLeftBounds());
+ getTopLeftContentBounds().set(getTopLeftBounds());
mWinToken1 = task1.token;
boundsChanged = true;
}
- if (!mBounds2.equals(mWinBounds2) || !task2.token.equals(mWinToken2)) {
- setTaskBounds(wct, task2, mBounds2);
- mWinBounds2.set(mBounds2);
+ if (!getBottomRightBounds().equals(getBottomRightContentBounds())
+ || !task2.token.equals(mWinToken2)) {
+ setTaskBounds(wct, task2, getBottomRightBounds());
+ getBottomRightContentBounds().set(getBottomRightBounds());
mWinToken2 = task2.token;
boundsChanged = true;
}
@@ -1129,22 +1153,22 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
public void applyLayoutOffsetTarget(WindowContainerTransaction wct, int offsetX, int offsetY,
ActivityManager.RunningTaskInfo taskInfo1, ActivityManager.RunningTaskInfo taskInfo2) {
if (offsetX == 0 && offsetY == 0) {
- wct.setBounds(taskInfo1.token, mBounds1);
+ wct.setBounds(taskInfo1.token, getTopLeftBounds());
wct.setScreenSizeDp(taskInfo1.token,
SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
- wct.setBounds(taskInfo2.token, mBounds2);
+ wct.setBounds(taskInfo2.token, getBottomRightBounds());
wct.setScreenSizeDp(taskInfo2.token,
SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
} else {
- getBounds1(mTempRect);
+ copyTopLeftBounds(mTempRect);
mTempRect.offset(offsetX, offsetY);
wct.setBounds(taskInfo1.token, mTempRect);
wct.setScreenSizeDp(taskInfo1.token,
taskInfo1.configuration.screenWidthDp,
taskInfo1.configuration.screenHeightDp);
- getBounds2(mTempRect);
+ copyBottomRightBounds(mTempRect);
mTempRect.offset(offsetX, offsetY);
wct.setBounds(taskInfo2.token, mTempRect);
wct.setScreenSizeDp(taskInfo2.token,
@@ -1162,9 +1186,9 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
pw.println(innerPrefix + "mFreezeDividerWindow=" + mFreezeDividerWindow);
pw.println(innerPrefix + "mDimNonImeSide=" + mDimNonImeSide);
pw.println(innerPrefix + "mDividerPosition=" + mDividerPosition);
- pw.println(innerPrefix + "bounds1=" + mBounds1.toShortString());
+ pw.println(innerPrefix + "bounds1=" + getTopLeftBounds().toShortString());
pw.println(innerPrefix + "dividerBounds=" + mDividerBounds.toShortString());
- pw.println(innerPrefix + "bounds2=" + mBounds2.toShortString());
+ pw.println(innerPrefix + "bounds2=" + getBottomRightBounds().toShortString());
}
/** Handles layout change event. */
@@ -1274,15 +1298,16 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
}
final boolean topLeftShrink = isLeftRightSplit
- ? position < mWinBounds1.right : position < mWinBounds1.bottom;
+ ? position < getTopLeftContentBounds().right
+ : position < getTopLeftContentBounds().bottom;
if (topLeftShrink) {
mShrinkSide = isLeftRightSplit ? DOCKED_LEFT : DOCKED_TOP;
- mContentBounds.set(mWinBounds1);
- mSurfaceBounds.set(mBounds1);
+ mContentBounds.set(getTopLeftContentBounds());
+ mSurfaceBounds.set(getTopLeftBounds());
} else {
mShrinkSide = isLeftRightSplit ? DOCKED_RIGHT : DOCKED_BOTTOM;
- mContentBounds.set(mWinBounds2);
- mSurfaceBounds.set(mBounds2);
+ mContentBounds.set(getBottomRightContentBounds());
+ mSurfaceBounds.set(getBottomRightBounds());
}
if (mDismissingSide != DOCKED_INVALID) {
@@ -1334,12 +1359,12 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
case DOCKED_TOP:
case DOCKED_LEFT:
targetLeash = leash1;
- mTempRect.set(mBounds1);
+ mTempRect.set(getTopLeftBounds());
break;
case DOCKED_BOTTOM:
case DOCKED_RIGHT:
targetLeash = leash2;
- mTempRect.set(mBounds2);
+ mTempRect.set(getBottomRightBounds());
break;
}
} else if (mParallaxType == PARALLAX_ALIGN_CENTER) {
@@ -1347,12 +1372,12 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
case DOCKED_TOP:
case DOCKED_LEFT:
targetLeash = leash1;
- mTempRect.set(mBounds1);
+ mTempRect.set(getTopLeftBounds());
break;
case DOCKED_BOTTOM:
case DOCKED_RIGHT:
targetLeash = leash2;
- mTempRect.set(mBounds2);
+ mTempRect.set(getBottomRightBounds());
break;
}
}
@@ -1530,7 +1555,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
private int getTargetYOffset() {
final int desireOffset = Math.abs(mEndImeTop - mStartImeTop);
// Make sure to keep at least 30% visible for the top split.
- final int maxOffset = (int) (mBounds1.height() * ADJUSTED_SPLIT_FRACTION_MAX);
+ final int maxOffset = (int) (getTopLeftBounds().height() * ADJUSTED_SPLIT_FRACTION_MAX);
return -Math.min(desireOffset, maxOffset);
}
@@ -1580,11 +1605,11 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
t.setPosition(dividerLeash, mTempRect.left, mTempRect.top);
}
- getRefBounds1(mTempRect);
+ copyTopLeftRefBounds(mTempRect);
mTempRect.offset(0, mYOffsetForIme);
t.setPosition(leash1, mTempRect.left, mTempRect.top);
- getRefBounds2(mTempRect);
+ copyBottomRightRefBounds(mTempRect);
mTempRect.offset(0, mYOffsetForIme);
t.setPosition(leash2, mTempRect.left, mTempRect.top);
adjusted = true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 0a39076a8768..a0bdd9fad510 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -70,6 +70,8 @@ import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.policy.ScreenDecorationsUtils
import com.android.internal.protolog.ProtoLog
import com.android.window.flags.Flags
+import com.android.window.flags.Flags.enableMoveToNextDisplayShortcut
+import com.android.wm.shell.Flags.enableFlexibleSplit
import com.android.wm.shell.R
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
@@ -102,6 +104,7 @@ import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.DESKTOP_DENSITY_OVERRIDE
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.useDesktopOverrideDensity
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
+import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_UNDEFINED
import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
import com.android.wm.shell.splitscreen.SplitScreenController
@@ -1512,11 +1515,15 @@ class DesktopTasksController(
WINDOWING_MODE_MULTI_WINDOW -> {
val splitPosition = splitScreenController
.determineNewInstancePosition(callingTaskInfo)
+ // TODO(b/349828130) currently pass in index_undefined until we can revisit these
+ // specific cases in the future.
+ val splitIndex = if (enableFlexibleSplit())
+ splitScreenController.determineNewInstanceIndex(callingTaskInfo) else
+ SPLIT_INDEX_UNDEFINED
splitScreenController.startIntent(
launchIntent, context.userId, fillIn, splitPosition,
options.toBundle(), null /* hideTaskToken */,
- true /* forceLaunchNewTask */
- )
+ true /* forceLaunchNewTask */, splitIndex)
}
WINDOWING_MODE_FREEFORM -> {
val wct = WindowContainerTransaction()
@@ -1661,6 +1668,13 @@ class DesktopTasksController(
transition, wct, task.displayId
)
}
+ } else if (taskRepository.isActiveTask(task.taskId)) {
+ // If a freeform task receives a request for a fullscreen launch, apply the same
+ // changes we do for similar transitions. The task not having WINDOWING_MODE_UNDEFINED
+ // set when needed can interfere with future split / multi-instance transitions.
+ return WindowContainerTransaction().also { wct ->
+ addMoveToFullscreenChanges(wct, task)
+ }
}
return null
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/SplitDragPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/SplitDragPolicy.java
index 5d22c1edf8fe..ae9d21f621de 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/SplitDragPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/SplitDragPolicy.java
@@ -41,6 +41,9 @@ import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT
import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_TOP;
import static com.android.wm.shell.shared.draganddrop.DragAndDropConstants.EXTRA_DISALLOW_HIT_REGION;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_0;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_1;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_UNDEFINED;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
@@ -81,6 +84,7 @@ import com.android.wm.shell.draganddrop.anim.HoverAnimProps;
import com.android.wm.shell.draganddrop.anim.TwoFiftyFiftyTargetAnimator;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.split.SplitScreenConstants;
+import com.android.wm.shell.shared.split.SplitScreenConstants.SplitIndex;
import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -219,8 +223,10 @@ public class SplitDragPolicy implements DropTarget {
displayRegion.splitHorizontally(startHitRegion, endHitRegion);
}
- mTargets.add(new Target(TYPE_SPLIT_LEFT, startHitRegion, startBounds, -1));
- mTargets.add(new Target(TYPE_SPLIT_RIGHT, endHitRegion, endBounds, -1));
+ mTargets.add(new Target(TYPE_SPLIT_LEFT, startHitRegion, startBounds,
+ SPLIT_INDEX_0));
+ mTargets.add(new Target(TYPE_SPLIT_RIGHT, endHitRegion, endBounds,
+ SPLIT_INDEX_1));
} else {
// TODO(b/349828130), move this into init function and/or the insets updating
// callback
@@ -287,9 +293,10 @@ public class SplitDragPolicy implements DropTarget {
displayRegion.splitVertically(leftHitRegion, rightHitRegion);
}
- mTargets.add(new Target(TYPE_SPLIT_LEFT, leftHitRegion, topOrLeftBounds, -1));
+ mTargets.add(new Target(TYPE_SPLIT_LEFT, leftHitRegion, topOrLeftBounds,
+ SPLIT_INDEX_UNDEFINED));
mTargets.add(new Target(TYPE_SPLIT_RIGHT, rightHitRegion, bottomOrRightBounds,
- -1));
+ SPLIT_INDEX_UNDEFINED));
} else {
final Rect topHitRegion = new Rect();
final Rect bottomHitRegion = new Rect();
@@ -308,9 +315,10 @@ public class SplitDragPolicy implements DropTarget {
displayRegion.splitHorizontally(topHitRegion, bottomHitRegion);
}
- mTargets.add(new Target(TYPE_SPLIT_TOP, topHitRegion, topOrLeftBounds, -1));
+ mTargets.add(new Target(TYPE_SPLIT_TOP, topHitRegion, topOrLeftBounds,
+ SPLIT_INDEX_UNDEFINED));
mTargets.add(new Target(TYPE_SPLIT_BOTTOM, bottomHitRegion, bottomOrRightBounds,
- -1));
+ SPLIT_INDEX_UNDEFINED));
}
}
} else {
@@ -378,9 +386,9 @@ public class SplitDragPolicy implements DropTarget {
? mFullscreenStarter
: mSplitscreenStarter;
if (mSession.appData != null) {
- launchApp(mSession, starter, position, hideTaskToken);
+ launchApp(mSession, starter, position, hideTaskToken, target.index);
} else {
- launchIntent(mSession, starter, position, hideTaskToken);
+ launchIntent(mSession, starter, position, hideTaskToken, target.index);
}
if (enableFlexibleSplit()) {
@@ -392,9 +400,10 @@ public class SplitDragPolicy implements DropTarget {
* Launches an app provided by SysUI.
*/
private void launchApp(DragSession session, Starter starter, @SplitPosition int position,
- @Nullable WindowContainerToken hideTaskToken) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Launching app data at position=%d",
- position);
+ @Nullable WindowContainerToken hideTaskToken, @SplitIndex int splitIndex) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Launching app data at position=%d index=%d",
+ position, splitIndex);
final ClipDescription description = session.getClipDescription();
final boolean isTask = description.hasMimeType(MIMETYPE_APPLICATION_TASK);
final boolean isShortcut = description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT);
@@ -429,7 +438,7 @@ public class SplitDragPolicy implements DropTarget {
}
}
starter.startIntent(launchIntent, user.getIdentifier(), null /* fillIntent */,
- position, opts, hideTaskToken);
+ position, opts, hideTaskToken, splitIndex);
}
}
@@ -437,7 +446,7 @@ public class SplitDragPolicy implements DropTarget {
* Launches an intent sender provided by an application.
*/
private void launchIntent(DragSession session, Starter starter, @SplitPosition int position,
- @Nullable WindowContainerToken hideTaskToken) {
+ @Nullable WindowContainerToken hideTaskToken, @SplitIndex int index) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Launching intent at position=%d",
position);
final ActivityOptions baseActivityOpts = ActivityOptions.makeBasic();
@@ -452,7 +461,7 @@ public class SplitDragPolicy implements DropTarget {
final Bundle opts = baseActivityOpts.toBundle();
starter.startIntent(session.launchableIntent,
session.launchableIntent.getCreatorUserHandle().getIdentifier(),
- null /* fillIntent */, position, opts, hideTaskToken);
+ null /* fillIntent */, position, opts, hideTaskToken, index);
}
@Override
@@ -541,7 +550,7 @@ public class SplitDragPolicy implements DropTarget {
@Nullable Bundle options, UserHandle user);
void startIntent(PendingIntent intent, int userId, Intent fillInIntent,
@SplitPosition int position, @Nullable Bundle options,
- @Nullable WindowContainerToken hideTaskToken);
+ @Nullable WindowContainerToken hideTaskToken, @SplitIndex int index);
void enterSplitScreen(int taskId, boolean leftOrTop);
/**
@@ -592,7 +601,7 @@ public class SplitDragPolicy implements DropTarget {
@Override
public void startIntent(PendingIntent intent, int userId, @Nullable Intent fillInIntent,
int position, @Nullable Bundle options,
- @Nullable WindowContainerToken hideTaskToken) {
+ @Nullable WindowContainerToken hideTaskToken, @SplitIndex int index) {
if (hideTaskToken != null) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
"Default starter does not support hide task token");
@@ -641,13 +650,13 @@ public class SplitDragPolicy implements DropTarget {
final Rect hitRegion;
// The approximate visual region for where the task will start
final Rect drawRegion;
- int index;
+ @SplitIndex int index;
/**
* @param index 0-indexed, represents which position of drop target this object represents,
* 0 to N for left to right, top to bottom
*/
- public Target(@Type int t, Rect hit, Rect draw, int index) {
+ public Target(@Type int t, Rect hit, Rect draw, @SplitIndex int index) {
type = t;
hitRegion = hit;
drawRegion = draw;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/anim/TwoFiftyFiftyTargetAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/anim/TwoFiftyFiftyTargetAnimator.kt
index 9f532f57961d..54619528f2bd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/anim/TwoFiftyFiftyTargetAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/anim/TwoFiftyFiftyTargetAnimator.kt
@@ -22,6 +22,10 @@ import android.graphics.Rect
import com.android.wm.shell.R
import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.draganddrop.SplitDragPolicy.Target
+import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_0
+import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_1
+import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_2
+import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_3
/**
* Represents Drop Zone targets and animations for when the system is currently in a 2 app 50/50
@@ -98,7 +102,7 @@ class TwoFiftyFiftyTargetAnimator : DropTargetAnimSupplier {
farStartBounds.right + halfDividerWidth,
farStartBounds.bottom
),
- farStartBounds, 0
+ farStartBounds, SPLIT_INDEX_0
)
)
targets.add(
@@ -110,7 +114,7 @@ class TwoFiftyFiftyTargetAnimator : DropTargetAnimSupplier {
startBounds.right + halfDividerWidth,
startBounds.bottom
),
- startBounds, 1
+ startBounds, SPLIT_INDEX_1
)
)
targets.add(
@@ -120,7 +124,7 @@ class TwoFiftyFiftyTargetAnimator : DropTargetAnimSupplier {
endBounds.left - halfDividerWidth,
endBounds.top, endBounds.right, endBounds.bottom
),
- endBounds, 2
+ endBounds, SPLIT_INDEX_2
)
)
targets.add(
@@ -130,7 +134,7 @@ class TwoFiftyFiftyTargetAnimator : DropTargetAnimSupplier {
farEndBounds.left - halfDividerWidth,
farEndBounds.top, farEndBounds.right, farEndBounds.bottom
),
- farEndBounds, 3
+ farEndBounds, SPLIT_INDEX_3
)
)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
index 319bfac734ba..e4f83333edbf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -173,6 +173,10 @@ public class KeyguardTransitionHandler
return mKeyguardShowing;
}
+ public boolean isKeyguardAnimating() {
+ return !mStartedTransitions.isEmpty();
+ }
+
@Override
public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
mDreamToken = taskInfo.getActivityType() == ACTIVITY_TYPE_DREAM ? taskInfo.token : null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index 3e6d36ce0ca3..39ed9abcfd26 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -54,10 +54,27 @@ public interface SplitScreen {
*/
int STAGE_TYPE_SIDE = 1;
+ /**
+ * Position independent stage identifier for a given Stage
+ */
+ int STAGE_TYPE_A = 2;
+ /**
+ * Position independent stage identifier for a given Stage
+ */
+ int STAGE_TYPE_B = 3;
+ /**
+ * Position independent stage identifier for a given Stage
+ */
+ int STAGE_TYPE_C = 4;
+
@IntDef(prefix = { "STAGE_TYPE_" }, value = {
STAGE_TYPE_UNDEFINED,
STAGE_TYPE_MAIN,
- STAGE_TYPE_SIDE
+ STAGE_TYPE_SIDE,
+ // Used for flexible split
+ STAGE_TYPE_A,
+ STAGE_TYPE_B,
+ STAGE_TYPE_C
})
@interface StageType {}
@@ -128,6 +145,9 @@ public interface SplitScreen {
case STAGE_TYPE_UNDEFINED: return "UNDEFINED";
case STAGE_TYPE_MAIN: return "MAIN";
case STAGE_TYPE_SIDE: return "SIDE";
+ case STAGE_TYPE_A: return "STAGE_A";
+ case STAGE_TYPE_B: return "STAGE_B";
+ case STAGE_TYPE_C: return "STAGE_C";
default: return "UNKNOWN(" + stage + ")";
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 6398d31b4f82..4f0f6760a951 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -24,6 +24,7 @@ import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.wm.shell.Flags.enableFlexibleSplit;
import static com.android.wm.shell.common.MultiInstanceHelper.getComponent;
import static com.android.wm.shell.common.MultiInstanceHelper.getShortcutComponent;
import static com.android.wm.shell.common.MultiInstanceHelper.samePackage;
@@ -33,6 +34,9 @@ import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMes
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
import static com.android.wm.shell.shared.split.SplitScreenConstants.KEY_EXTRA_WIDGET_INTENT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_0;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_1;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_UNDEFINED;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
@@ -98,6 +102,7 @@ import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.shared.TransactionPool;
import com.android.wm.shell.shared.annotations.ExternalThread;
import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition;
+import com.android.wm.shell.shared.split.SplitScreenConstants.SplitIndex;
import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.sysui.KeyguardChangeListener;
@@ -325,7 +330,6 @@ public class SplitScreenController implements SplitDragPolicy.Starter,
/**
* @return an Array of RunningTaskInfo's ordered by leftToRight or topTopBottom
*/
- @Nullable
public ActivityManager.RunningTaskInfo[] getAllTaskInfos() {
// TODO(b/349828130) Add the third stage task info and not rely on positions
ActivityManager.RunningTaskInfo topLeftTask = getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
@@ -335,7 +339,7 @@ public class SplitScreenController implements SplitDragPolicy.Starter,
return new ActivityManager.RunningTaskInfo[]{topLeftTask, bottomRightTask};
}
- return null;
+ return new ActivityManager.RunningTaskInfo[0];
}
/** Check task is under split or not by taskId. */
@@ -405,7 +409,7 @@ public class SplitScreenController implements SplitDragPolicy.Starter,
public void prepareEnterSplitScreen(WindowContainerTransaction wct,
ActivityManager.RunningTaskInfo taskInfo, int startPosition) {
mStageCoordinator.prepareEnterSplitScreen(wct, taskInfo, startPosition,
- false /* resizeAnim */);
+ false /* resizeAnim */, SPLIT_INDEX_UNDEFINED);
}
/**
@@ -451,6 +455,24 @@ public class SplitScreenController implements SplitDragPolicy.Starter,
}
}
+ /**
+ * Determines which split index a new instance of a task should take.
+ * @param callingTask The task requesting a new instance.
+ * @return the split index of the new instance
+ */
+ @SplitIndex
+ public int determineNewInstanceIndex(@NonNull ActivityManager.RunningTaskInfo callingTask) {
+ if (!enableFlexibleSplit()) {
+ throw new IllegalStateException("Use determineNewInstancePosition");
+ }
+ if (callingTask.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+ || getSplitPosition(callingTask.taskId) == SPLIT_POSITION_TOP_OR_LEFT) {
+ return SPLIT_INDEX_1;
+ } else {
+ return SPLIT_INDEX_0;
+ }
+ }
+
public void enterSplitScreen(int taskId, boolean leftOrTop) {
enterSplitScreen(taskId, leftOrTop, new WindowContainerTransaction());
}
@@ -685,7 +707,10 @@ public class SplitScreenController implements SplitDragPolicy.Starter,
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startIntentWithInstanceId: reason=%d",
ENTER_REASON_LAUNCHER);
mStageCoordinator.getLogger().enterRequested(instanceId, ENTER_REASON_LAUNCHER);
- startIntent(intent, userId, fillInIntent, position, options, null /* hideTaskToken */);
+ // TODO(b/349828130) currently pass in index_undefined until we can revisit these
+ // specific cases in the future. Only focusing on parity with starting intent/task
+ startIntent(intent, userId, fillInIntent, position, options, null /* hideTaskToken */,
+ SPLIT_INDEX_UNDEFINED);
}
private void startIntentAndTask(PendingIntent pendingIntent, int userId1,
@@ -775,9 +800,9 @@ public class SplitScreenController implements SplitDragPolicy.Starter,
@Override
public void startIntent(PendingIntent intent, int userId1, @Nullable Intent fillInIntent,
@SplitPosition int position, @Nullable Bundle options,
- @Nullable WindowContainerToken hideTaskToken) {
+ @Nullable WindowContainerToken hideTaskToken, @SplitIndex int index) {
startIntent(intent, userId1, fillInIntent, position, options, hideTaskToken,
- false /* forceLaunchNewTask */);
+ false /* forceLaunchNewTask */, index);
}
/**
@@ -790,7 +815,8 @@ public class SplitScreenController implements SplitDragPolicy.Starter,
*/
public void startIntent(PendingIntent intent, int userId1, @Nullable Intent fillInIntent,
@SplitPosition int position, @Nullable Bundle options,
- @Nullable WindowContainerToken hideTaskToken, boolean forceLaunchNewTask) {
+ @Nullable WindowContainerToken hideTaskToken, boolean forceLaunchNewTask,
+ @SplitIndex int index) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"startIntent(): intent=%s user=%d fillInIntent=%s position=%d", intent, userId1,
fillInIntent, position);
@@ -816,7 +842,7 @@ public class SplitScreenController implements SplitDragPolicy.Starter,
if (taskInfo != null) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Found suitable background task=%s", taskInfo);
- mStageCoordinator.startTask(taskInfo.taskId, position, options, hideTaskToken);
+ mStageCoordinator.startTask(taskInfo.taskId, position, options, hideTaskToken, index);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Start task in background");
return;
@@ -841,7 +867,8 @@ public class SplitScreenController implements SplitDragPolicy.Starter,
}
}
- mStageCoordinator.startIntent(intent, fillInIntent, position, options, hideTaskToken);
+ mStageCoordinator.startIntent(intent, fillInIntent, position, options, hideTaskToken,
+ index);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 840049412db4..3091be574a53 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -21,6 +21,7 @@ import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static com.android.wm.shell.Flags.enableFlexibleSplit;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TRANSITIONS;
import static com.android.wm.shell.shared.animation.Interpolators.ALPHA_IN;
@@ -55,6 +56,9 @@ import com.android.wm.shell.transition.OneShotRemoteHandler;
import com.android.wm.shell.transition.Transitions;
import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
import java.util.concurrent.Executor;
/** Manages transition animations for split-screen. */
@@ -268,22 +272,21 @@ class SplitScreenTransitions {
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback,
- @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot,
- @NonNull SplitDecorManager mainDecor, @NonNull SplitDecorManager sideDecor) {
+ @NonNull Map<WindowContainerToken, SplitDecorManager> rootDecorMap) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "playResizeAnimation: transition=%d", info.getDebugId());
initTransition(transition, finishTransaction, finishCallback);
+ Set<WindowContainerToken> rootDecorKeys = rootDecorMap.keySet();
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
- if (mainRoot.equals(change.getContainer()) || sideRoot.equals(change.getContainer())) {
+ if (rootDecorKeys.contains(change.getContainer())) {
final SurfaceControl leash = change.getLeash();
startTransaction.setPosition(leash, change.getEndAbsBounds().left,
change.getEndAbsBounds().top);
startTransaction.setWindowCrop(leash, change.getEndAbsBounds().width(),
change.getEndAbsBounds().height());
- SplitDecorManager decor = mainRoot.equals(change.getContainer())
- ? mainDecor : sideDecor;
+ SplitDecorManager decor = rootDecorMap.get(change.getContainer());
// This is to ensure onFinished be called after all animations ended.
ValueAnimator va = new ValueAnimator();
@@ -433,15 +436,22 @@ class SplitScreenTransitions {
Transitions.TransitionHandler handler,
@Nullable TransitionConsumedCallback consumedCallback,
@Nullable TransitionFinishedCallback finishCallback,
- @NonNull SplitDecorManager mainDecor, @NonNull SplitDecorManager sideDecor) {
+ @Nullable SplitDecorManager mainDecor, @Nullable SplitDecorManager sideDecor,
+ @Nullable List<SplitDecorManager> decorManagers) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
" splitTransition deduced Resize split screen.");
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setResizeTransition: hasPendingResize=%b",
mPendingResize != null);
if (mPendingResize != null) {
mPendingResize.cancel(null);
- mainDecor.cancelRunningAnimations();
- sideDecor.cancelRunningAnimations();
+ if (enableFlexibleSplit()) {
+ for (SplitDecorManager stage : decorManagers) {
+ stage.cancelRunningAnimations();
+ }
+ } else {
+ mainDecor.cancelRunningAnimations();
+ sideDecor.cancelRunningAnimations();
+ }
mAnimations.clear();
onFinish(null /* wct */);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index e692c61cd493..45ecfa95b494 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
@@ -33,6 +33,7 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
+import static com.android.wm.shell.Flags.enableFlexibleSplit;
import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_ALIGN_CENTER;
import static com.android.wm.shell.common.split.SplitScreenUtils.isPartiallyOffscreen;
import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
@@ -43,6 +44,7 @@ import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
import static com.android.wm.shell.shared.TransitionUtil.isOrderOnly;
import static com.android.wm.shell.shared.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_10_90;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_90_10;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_0;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_1;
@@ -51,9 +53,12 @@ import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSIT
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
import static com.android.wm.shell.shared.split.SplitScreenConstants.splitPositionToString;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_A;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_B;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
+import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString;
import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_LAUNCHER;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED;
@@ -142,6 +147,7 @@ import com.android.wm.shell.shared.TransactionPool;
import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.shared.split.SplitBounds;
import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition;
+import com.android.wm.shell.shared.split.SplitScreenConstants.SplitIndex;
import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason;
@@ -153,11 +159,15 @@ import dalvik.annotation.optimization.NeverCompile;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
/**
* Coordinates the staging (visibility, sizing, ...) of the split-screen stages.
@@ -178,10 +188,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// entered
private static final int DISABLE_LAUNCH_ADJACENT_AFTER_ENTER_TIMEOUT_MS = 1000;
- private final StageTaskListener mMainStage;
- private final StageTaskListener mSideStage;
+ private StageTaskListener mMainStage;
+ private StageTaskListener mSideStage;
@SplitPosition
private int mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT;
+ private StageOrderOperator mStageOrderOperator;
private final int mDisplayId;
private SplitLayout mSplitLayout;
@@ -335,22 +346,32 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
taskOrganizer.createRootTask(displayId, WINDOWING_MODE_FULLSCREEN, this /* listener */);
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Creating main/side root task");
- mMainStage = new StageTaskListener(
- mContext,
- mTaskOrganizer,
- mDisplayId,
- this /*stageListenerCallbacks*/,
- mSyncQueue,
- iconProvider,
- mWindowDecorViewModel);
- mSideStage = new StageTaskListener(
- mContext,
- mTaskOrganizer,
- mDisplayId,
- this /*stageListenerCallbacks*/,
- mSyncQueue,
- iconProvider,
- mWindowDecorViewModel);
+ if (enableFlexibleSplit()) {
+ mStageOrderOperator = new StageOrderOperator(mContext,
+ mTaskOrganizer,
+ mDisplayId,
+ this /*stageListenerCallbacks*/,
+ mSyncQueue,
+ iconProvider,
+ mWindowDecorViewModel);
+ } else {
+ mMainStage = new StageTaskListener(
+ mContext,
+ mTaskOrganizer,
+ mDisplayId,
+ this /*stageListenerCallbacks*/,
+ mSyncQueue,
+ iconProvider,
+ mWindowDecorViewModel, STAGE_TYPE_MAIN);
+ mSideStage = new StageTaskListener(
+ mContext,
+ mTaskOrganizer,
+ mDisplayId,
+ this /*stageListenerCallbacks*/,
+ mSyncQueue,
+ iconProvider,
+ mWindowDecorViewModel, STAGE_TYPE_SIDE);
+ }
mDisplayController = displayController;
mDisplayImeController = displayImeController;
mDisplayInsetsController = displayInsetsController;
@@ -422,24 +443,63 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
public boolean isSplitScreenVisible() {
- return mSideStage.mVisible && mMainStage.mVisible;
+ if (enableFlexibleSplit()) {
+ return runForActiveStagesAllMatch((stage) -> stage.mVisible);
+ } else {
+ return mSideStage.mVisible && mMainStage.mVisible;
+ }
}
- private void activateSplit(WindowContainerTransaction wct, boolean includingTopTask) {
- mMainStage.activate(wct, includingTopTask);
+ /**
+ * @param includingTopTask reparents the current top task into the stage defined by index
+ * (or mainStage in legacy split)
+ * @param index the index to move the current visible task into, if undefined will arbitrarily
+ * choose a stage to launch into
+ */
+ private void activateSplit(WindowContainerTransaction wct, boolean includingTopTask,
+ int index) {
+ if (enableFlexibleSplit()) {
+ mStageOrderOperator.onEnteringSplit(SNAP_TO_2_50_50);
+ if (index == SPLIT_INDEX_UNDEFINED || !includingTopTask) {
+ // If we aren't includingTopTask, then the call to activate on the stage is
+ // effectively a no-op. Previously the stage kept track of the "isActive" state,
+ // but now that gets set in the "onEnteringSplit" call above.
+ //
+ // index == UNDEFINED case might change, but as of now no use case where we activate
+ // without an index specified.
+ return;
+ }
+ @SplitIndex int oppositeIndex = index == SPLIT_INDEX_0 ? SPLIT_INDEX_1 : SPLIT_INDEX_0;
+ StageTaskListener activatingStage = mStageOrderOperator.getStageForIndex(oppositeIndex);
+ activatingStage.activate(wct, includingTopTask);
+ } else {
+ mMainStage.activate(wct, includingTopTask);
+ }
}
public boolean isSplitActive() {
- return mMainStage.isActive();
+ if (enableFlexibleSplit()) {
+ return mStageOrderOperator.isActive();
+ } else {
+ return mMainStage.isActive();
+ }
}
/**
* Deactivates main stage by removing the stage from the top level split root (usually when a
* task underneath gets removed from the stage root).
- * @param reparentToTop whether we want to put the stage root back on top
+ * @param stageToTop stage which we want to put on top
*/
- private void deactivateSplit(WindowContainerTransaction wct, boolean reparentToTop) {
- mMainStage.deactivate(wct, reparentToTop);
+ private void deactivateSplit(WindowContainerTransaction wct, @StageType int stageToTop) {
+ if (enableFlexibleSplit()) {
+ StageTaskListener stageToDeactivate = mStageOrderOperator.getAllStages().stream()
+ .filter(stage -> stage.getId() == stageToTop)
+ .findFirst().orElseThrow();
+ stageToDeactivate.deactivate(wct, true /*toTop*/);
+ mStageOrderOperator.onExitingSplit();
+ } else {
+ mMainStage.deactivate(wct, stageToTop == STAGE_TYPE_MAIN);
+ }
}
/** @return whether this transition-request has the launch-adjacent flag. */
@@ -463,11 +523,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// If one of the splitting tasks support auto-pip, wm-core might reparent the task to TDA
// and file a TRANSIT_PIP transition when finishing transitions.
// @see com.android.server.wm.RootWindowContainer#moveActivityToPinnedRootTask
- if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
- return true;
+ if (enableFlexibleSplit()) {
+ return mStageOrderOperator.getActiveStages().stream()
+ .anyMatch(stage -> stage.getChildCount() == 0);
+ } else {
+ return mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0;
}
-
- return false;
}
/** Checks if `transition` is a pending enter-split transition. */
@@ -477,10 +538,19 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@StageType
int getStageOfTask(int taskId) {
- if (mMainStage.containsTask(taskId)) {
- return STAGE_TYPE_MAIN;
- } else if (mSideStage.containsTask(taskId)) {
- return STAGE_TYPE_SIDE;
+ if (enableFlexibleSplit()) {
+ StageTaskListener stageTaskListener = mStageOrderOperator.getActiveStages().stream()
+ .filter(stage -> stage.containsTask(taskId))
+ .findFirst().orElse(null);
+ if (stageTaskListener != null) {
+ return stageTaskListener.getId();
+ }
+ } else {
+ if (mMainStage.containsTask(taskId)) {
+ return STAGE_TYPE_MAIN;
+ } else if (mSideStage.containsTask(taskId)) {
+ return STAGE_TYPE_SIDE;
+ }
}
return STAGE_TYPE_UNDEFINED;
@@ -490,14 +560,22 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (mRootTaskInfo != null && mRootTaskInfo.taskId == taskId) {
return true;
}
- return mMainStage.isRootTaskId(taskId) || mSideStage.isRootTaskId(taskId);
+ if (enableFlexibleSplit()) {
+ return mStageOrderOperator.getActiveStages().stream()
+ .anyMatch((stage) -> stage.isRootTaskId(taskId));
+ } else {
+ return mMainStage.isRootTaskId(taskId) || mSideStage.isRootTaskId(taskId);
+ }
}
boolean moveToStage(ActivityManager.RunningTaskInfo task, @SplitPosition int stagePosition,
WindowContainerTransaction wct) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "moveToStage: task=%d position=%d", task.taskId,
stagePosition);
- prepareEnterSplitScreen(wct, task, stagePosition, false /* resizeAnim */);
+ // TODO(b/349828130) currently pass in index_undefined until we can revisit these
+ // specific cases in the future. Only focusing on parity with starting intent/task
+ prepareEnterSplitScreen(wct, task, stagePosition, false /* resizeAnim */,
+ SPLIT_INDEX_UNDEFINED);
mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct,
null, this,
isSplitScreenVisible()
@@ -595,11 +673,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
* same window container transaction as the starting of the intent.
*/
void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options,
- @Nullable WindowContainerToken hideTaskToken) {
- ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startTask: task=%d position=%d", taskId, position);
+ @Nullable WindowContainerToken hideTaskToken, @SplitIndex int index) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startTask: task=%d position=%d index=%d",
+ taskId, position, index);
mSplitRequest = new SplitRequest(taskId, position);
final WindowContainerTransaction wct = new WindowContainerTransaction();
- options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */);
+ options = enableFlexibleSplit()
+ ? resolveStartStageForIndex(options, null /*wct*/, index)
+ : resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */);
if (hideTaskToken != null) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Reordering hide-task to bottom");
wct.reorder(hideTaskToken, false /* onTop */);
@@ -623,7 +704,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// If split screen is not activated, we're expecting to open a pair of apps to split.
final int extraTransitType = isSplitActive()
? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
- prepareEnterSplitScreen(wct, null /* taskInfo */, position, !mIsDropEntering);
+ prepareEnterSplitScreen(wct, null /* taskInfo */, position, !mIsDropEntering, index);
mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, null, this,
extraTransitType, !mIsDropEntering);
@@ -635,13 +716,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
* same window container transaction as the starting of the intent.
*/
void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
- @Nullable Bundle options, @Nullable WindowContainerToken hideTaskToken) {
+ @Nullable Bundle options, @Nullable WindowContainerToken hideTaskToken,
+ @SplitIndex int index) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "startIntent: intent=%s position=%d", intent.getIntent(),
position);
mSplitRequest = new SplitRequest(intent.getIntent(), position);
final WindowContainerTransaction wct = new WindowContainerTransaction();
- options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */);
+ options = enableFlexibleSplit()
+ ? resolveStartStageForIndex(options, null /*wct*/, index)
+ : resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */);
if (hideTaskToken != null) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Reordering hide-task to bottom");
wct.reorder(hideTaskToken, false /* onTop */);
@@ -666,13 +750,17 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// If split screen is not activated, we're expecting to open a pair of apps to split.
final int extraTransitType = isSplitActive()
? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
- prepareEnterSplitScreen(wct, null /* taskInfo */, position, !mIsDropEntering);
+ prepareEnterSplitScreen(wct, null /* taskInfo */, position, !mIsDropEntering, index);
mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, null, this,
extraTransitType, !mIsDropEntering);
}
- /** Starts 2 tasks in one transition. */
+ /**
+ * Starts 2 tasks in one transition.
+ * @param taskId1 starts in the mSideStage
+ * @param taskId2 starts in the mainStage #startWithTask()
+ */
void startTasks(int taskId1, @Nullable Bundle options1, int taskId2, @Nullable Bundle options2,
@SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
@@ -687,11 +775,19 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
setSideStagePosition(splitPosition, wct);
options1 = options1 != null ? options1 : new Bundle();
- addActivityOptions(options1, mSideStage);
+ StageTaskListener stageForTask1;
+ if (enableFlexibleSplit()) {
+ stageForTask1 = mStageOrderOperator.getStageForLegacyPosition(splitPosition,
+ true /*checkAllStagesIfNotActive*/);
+ } else {
+ stageForTask1 = mSideStage;
+ }
+ addActivityOptions(options1, stageForTask1);
prepareTasksForSplitScreen(new int[] {taskId1, taskId2}, wct);
wct.startTask(taskId1, options1);
- startWithTask(wct, taskId2, options2, snapPosition, remoteTransition, instanceId);
+ startWithTask(wct, taskId2, options2, snapPosition, remoteTransition, instanceId,
+ splitPosition);
}
/** Start an intent and a task to a split pair in one transition. */
@@ -721,7 +817,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
prepareTasksForSplitScreen(new int[] {taskId}, wct);
- startWithTask(wct, taskId, options2, snapPosition, remoteTransition, instanceId);
+ startWithTask(wct, taskId, options2, snapPosition, remoteTransition, instanceId,
+ splitPosition);
}
/**
@@ -765,7 +862,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1);
prepareTasksForSplitScreen(new int[] {taskId}, wct);
- startWithTask(wct, taskId, options2, snapPosition, remoteTransition, instanceId);
+ startWithTask(wct, taskId, options2, snapPosition, remoteTransition, instanceId,
+ splitPosition);
}
/**
@@ -795,11 +893,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
*/
private void startWithTask(WindowContainerTransaction wct, int mainTaskId,
@Nullable Bundle mainOptions, @PersistentSnapPosition int snapPosition,
- @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ @Nullable RemoteTransition remoteTransition, InstanceId instanceId,
+ @SplitPosition int splitPosition) {
if (!isSplitActive()) {
// Build a request WCT that will launch both apps such that task 0 is on the main stage
// while task 1 is on the side stage.
- activateSplit(wct, false /* reparentToTop */);
+ // TODO(b/349828130) currently pass in index_undefined until we can revisit these
+ // specific cases in the future. Only focusing on parity with starting intent/task
+ activateSplit(wct, false /* reparentToTop */, SPLIT_INDEX_UNDEFINED);
}
mSplitLayout.setDivideRatio(snapPosition);
updateWindowBounds(mSplitLayout, wct);
@@ -807,10 +908,19 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
false /* reparentLeafTaskIfRelaunch */);
setRootForceTranslucent(false, wct);
-
+ // All callers of this method set the correct activity options on mSideStage,
+ // so we choose the opposite stage for this method
+ StageTaskListener stage;
+ if (enableFlexibleSplit()) {
+ stage = mStageOrderOperator
+ .getStageForLegacyPosition(reverseSplitPosition(splitPosition),
+ false /*checkAllStagesIfNotActive*/);
+ } else {
+ stage = mMainStage;
+ }
// Make sure the launch options will put tasks in the corresponding split roots
mainOptions = mainOptions != null ? mainOptions : new Bundle();
- addActivityOptions(mainOptions, mMainStage);
+ addActivityOptions(mainOptions, stage);
// Add task launch requests
wct.startTask(mainTaskId, mainOptions);
@@ -866,7 +976,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (!isSplitActive()) {
// Build a request WCT that will launch both apps such that task 0 is on the main stage
// while task 1 is on the side stage.
- activateSplit(wct, false /* reparentToTop */);
+ // TODO(b/349828130) currently pass in index_undefined until we can revisit these
+ // specific cases in the future. Only focusing on parity with starting intent/task
+ activateSplit(wct, false /* reparentToTop */, SPLIT_INDEX_UNDEFINED);
}
setSideStagePosition(splitPosition, wct);
@@ -974,6 +1086,31 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSideStage.evictInvisibleChildren(wct);
}
+ /**
+ * @param index for the new stage that will be opening. Ex. if app is dragged to
+ * index=1, then this will tell the stage at index=1 to launch the task
+ * in the wct in that stage. This doesn't verify that the non-specified
+ * indices' stages have their tasks correctly set/re-parented.
+ */
+ Bundle resolveStartStageForIndex(@Nullable Bundle options,
+ @Nullable WindowContainerTransaction wct,
+ @SplitIndex int index) {
+ StageTaskListener oppositeStage;
+ if (index == SPLIT_INDEX_UNDEFINED) {
+ // Arbitrarily choose a stage
+ oppositeStage = mStageOrderOperator.getStageForIndex(SPLIT_INDEX_1);
+ } else {
+ oppositeStage = mStageOrderOperator.getStageForIndex(index);
+ }
+ if (options == null) {
+ options = new Bundle();
+ }
+ updateStageWindowBoundsForIndex(wct, index);
+ addActivityOptions(options, oppositeStage);
+
+ return options;
+ }
+
Bundle resolveStartStage(@StageType int stage, @SplitPosition int position,
@Nullable Bundle options, @Nullable WindowContainerTransaction wct) {
switch (stage) {
@@ -1041,20 +1178,35 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return INVALID_TASK_ID;
}
- return mSideStagePosition == splitPosition
- ? mSideStage.getTopVisibleChildTaskId()
- : mMainStage.getTopVisibleChildTaskId();
+ if (enableFlexibleSplit()) {
+ StageTaskListener stage = mStageOrderOperator.getStageForLegacyPosition(splitPosition,
+ true /*checkAllStagesIfNotActive*/);
+ return stage != null ? stage.getTopVisibleChildTaskId() : INVALID_TASK_ID;
+ } else {
+ return mSideStagePosition == splitPosition
+ ? mSideStage.getTopVisibleChildTaskId()
+ : mMainStage.getTopVisibleChildTaskId();
+ }
}
void switchSplitPosition(String reason) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "switchSplitPosition");
final SurfaceControl.Transaction t = mTransactionPool.acquire();
mTempRect1.setEmpty();
- final StageTaskListener topLeftStage =
- mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
- final StageTaskListener bottomRightStage =
- mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
-
+ final StageTaskListener topLeftStage;
+ final StageTaskListener bottomRightStage;
+ if (enableFlexibleSplit()) {
+ topLeftStage = mStageOrderOperator.getStageForLegacyPosition(SPLIT_POSITION_TOP_OR_LEFT,
+ false /*checkAllStagesIfNotActive*/);
+ bottomRightStage = mStageOrderOperator
+ .getStageForLegacyPosition(SPLIT_POSITION_BOTTOM_OR_RIGHT,
+ false /*checkAllStagesIfNotActive*/);
+ } else {
+ topLeftStage =
+ mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
+ bottomRightStage =
+ mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
+ }
// Don't allow windows or divider to be focused during animation (mRootTaskInfo is the
// parent of all 3 leaves). We don't want the user to be able to tap and focus a window
// while it is moving across the screen, because granting focus also recalculates the
@@ -1091,9 +1243,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
});
ProtoLog.v(WM_SHELL_SPLIT_SCREEN, "Switch split position: %s", reason);
- mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
- getSideStagePosition(), mSideStage.getTopChildTaskUid(),
- mSplitLayout.isLeftRightSplit());
+ if (enableFlexibleSplit()) {
+ // TODO(b/374825718) update logging for 2+ apps
+ } else {
+ mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
+ getSideStagePosition(), mSideStage.getTopChildTaskUid(),
+ mSplitLayout.isLeftRightSplit());
+ }
}
void setSideStagePosition(@SplitPosition int sideStagePosition,
@@ -1101,8 +1257,26 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (mSideStagePosition == sideStagePosition) return;
mSideStagePosition = sideStagePosition;
sendOnStagePositionChanged();
+ StageTaskListener stage = enableFlexibleSplit()
+ ? mStageOrderOperator.getStageForLegacyPosition(mSideStagePosition,
+ true /*checkAllStagesIfNotActive*/)
+ : mSideStage;
- if (mSideStage.mVisible) {
+ if (stage.mVisible) {
+ if (wct == null) {
+ // onLayoutChanged builds/applies a wct with the contents of updateWindowBounds.
+ onLayoutSizeChanged(mSplitLayout);
+ } else {
+ updateWindowBounds(mSplitLayout, wct);
+ sendOnBoundsChanged();
+ }
+ }
+ }
+
+ private void updateStageWindowBoundsForIndex(@Nullable WindowContainerTransaction wct,
+ @SplitIndex int index) {
+ StageTaskListener stage = mStageOrderOperator.getStageForIndex(index);
+ if (stage.mVisible) {
if (wct == null) {
// onLayoutChanged builds/applies a wct with the contents of updateWindowBounds.
onLayoutSizeChanged(mSplitLayout);
@@ -1152,10 +1326,17 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
void recordLastActiveStage() {
if (!isSplitActive() || !isSplitScreenVisible()) {
mLastActiveStage = STAGE_TYPE_UNDEFINED;
- } else if (mMainStage.isFocused()) {
- mLastActiveStage = STAGE_TYPE_MAIN;
- } else if (mSideStage.isFocused()) {
- mLastActiveStage = STAGE_TYPE_SIDE;
+ } else if (enableFlexibleSplit()) {
+ mStageOrderOperator.getActiveStages().stream()
+ .filter(StageTaskListener::isFocused)
+ .findFirst()
+ .ifPresent(stage -> mLastActiveStage = stage.getId());
+ } else {
+ if (mMainStage.isFocused()) {
+ mLastActiveStage = STAGE_TYPE_MAIN;
+ } else if (mSideStage.isFocused()) {
+ mLastActiveStage = STAGE_TYPE_SIDE;
+ }
}
}
@@ -1212,7 +1393,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitLayout.getInvisibleBounds(mTempRect1);
if (childrenToTop == null || childrenToTop.getTopVisibleChildTaskId() == INVALID_TASK_ID) {
mSideStage.removeAllTasks(wct, false /* toTop */);
- deactivateSplit(wct, false /* reparentToTop */);
+ deactivateSplit(wct, STAGE_TYPE_UNDEFINED);
wct.reorder(mRootTaskInfo.token, false /* onTop */);
setRootForceTranslucent(true, wct);
wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
@@ -1241,7 +1422,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
childrenToTop.fadeOutDecor(() -> {
WindowContainerTransaction finishedWCT = new WindowContainerTransaction();
mIsExiting = false;
- deactivateSplit(finishedWCT, childrenToTop == mMainStage /* reparentToTop */);
+ deactivateSplit(finishedWCT, childrenToTop.getId());
mSideStage.removeAllTasks(finishedWCT, childrenToTop == mSideStage /* toTop */);
finishedWCT.reorder(mRootTaskInfo.token, false /* toTop */);
setRootForceTranslucent(true, finishedWCT);
@@ -1369,8 +1550,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mRecentTasks.ifPresent(recentTasks -> {
// Notify recents if we are exiting in a way that breaks the pair, and disable further
// updates to splits in the recents until we enter split again
- mMainStage.doForAllChildTasks(taskId -> recentTasks.removeSplitPair(taskId));
- mSideStage.doForAllChildTasks(taskId -> recentTasks.removeSplitPair(taskId));
+ if (enableFlexibleSplit()) {
+ runForActiveStages((stage) ->
+ stage.doForAllChildTasks(taskId -> recentTasks.removeSplitPair(taskId)));
+ } else {
+ mMainStage.doForAllChildTasks(taskId -> recentTasks.removeSplitPair(taskId));
+ mSideStage.doForAllChildTasks(taskId -> recentTasks.removeSplitPair(taskId));
+ }
});
logExit(exitReason);
}
@@ -1383,15 +1569,22 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
void prepareExitSplitScreen(@StageType int stageToTop,
@NonNull WindowContainerTransaction wct) {
if (!isSplitActive()) return;
- ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareExitSplitScreen: stageToTop=%d", stageToTop);
- mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE);
- deactivateSplit(wct, stageToTop == STAGE_TYPE_MAIN);
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareExitSplitScreen: stageToTop=%s",
+ stageTypeToString(stageToTop));
+ if (enableFlexibleSplit()) {
+ mStageOrderOperator.getActiveStages().stream()
+ .filter(stage -> stage.getId() != stageToTop)
+ .forEach(stage -> stage.removeAllTasks(wct, false /*toTop*/));
+ } else {
+ mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE);
+ }
+ deactivateSplit(wct, stageToTop);
}
private void prepareEnterSplitScreen(WindowContainerTransaction wct) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareEnterSplitScreen");
prepareEnterSplitScreen(wct, null /* taskInfo */, SPLIT_POSITION_UNDEFINED,
- !mIsDropEntering);
+ !mIsDropEntering, SPLIT_INDEX_UNDEFINED);
}
/**
@@ -1400,7 +1593,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
*/
void prepareEnterSplitScreen(WindowContainerTransaction wct,
@Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition,
- boolean resizeAnim) {
+ boolean resizeAnim, @SplitIndex int index) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareEnterSplitScreen: position=%d resize=%b",
startPosition, resizeAnim);
onSplitScreenEnter();
@@ -1412,7 +1605,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (isSplitActive()) {
prepareBringSplit(wct, taskInfo, startPosition, resizeAnim);
} else {
- prepareActiveSplit(wct, taskInfo, startPosition, resizeAnim);
+ prepareActiveSplit(wct, taskInfo, startPosition, resizeAnim, index);
}
}
@@ -1433,14 +1626,22 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (!mSkipEvictingMainStageChildren) {
mMainStage.evictAllChildren(wct);
}
- mMainStage.reparentTopTask(wct);
+ // TODO(b/349828130) revisit bring split from BG to FG scenarios
+ if (enableFlexibleSplit()) {
+ runForActiveStages(stage -> stage.reparentTopTask(wct));
+ } else {
+ mMainStage.reparentTopTask(wct);
+ }
prepareSplitLayout(wct, resizeAnim);
}
}
+ /**
+ * @param index The index that has already been assigned a stage
+ */
private void prepareActiveSplit(WindowContainerTransaction wct,
@Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition,
- boolean resizeAnim) {
+ boolean resizeAnim, @SplitIndex int index) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "prepareActiveSplit: task=%d isSplitVisible=%b",
taskInfo != null ? taskInfo.taskId : -1, isSplitScreenVisible());
// We handle split visibility itself on shell transition, but sometimes we didn't
@@ -1450,7 +1651,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
setSideStagePosition(startPosition, wct);
mSideStage.addTask(taskInfo, wct);
}
- activateSplit(wct, true /* reparentToTop */);
+ activateSplit(wct, true /* reparentToTop */, index);
prepareSplitLayout(wct, resizeAnim);
}
@@ -1477,8 +1678,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
void finishEnterSplitScreen(SurfaceControl.Transaction finishT) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "finishEnterSplitScreen");
mSplitLayout.update(null, true /* resetImePosition */);
- mMainStage.getSplitDecorManager().inflate(mContext, mMainStage.mRootLeash);
- mSideStage.getSplitDecorManager().inflate(mContext, mSideStage.mRootLeash);
+ if (enableFlexibleSplit()) {
+ runForActiveStages((stage) ->
+ stage.getSplitDecorManager().inflate(mContext, stage.mRootLeash));
+ } else {
+ mMainStage.getSplitDecorManager().inflate(mContext, mMainStage.mRootLeash);
+ mSideStage.getSplitDecorManager().inflate(mContext, mSideStage.mRootLeash);
+ }
setDividerVisibility(true, finishT);
// Ensure divider surface are re-parented back into the hierarchy at the end of the
// transition. See Transition#buildFinishTransaction for more detail.
@@ -1492,6 +1698,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitRequest = null;
updateRecentTasksSplitPair();
+ if (enableFlexibleSplit()) {
+ // TODO(b/374825718) log 2+ apps
+ return;
+ }
mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(),
getMainStagePosition(), mMainStage.getTopChildTaskUid(),
getSideStagePosition(), mSideStage.getTopChildTaskUid(),
@@ -1499,8 +1709,17 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) {
- outTopOrLeftBounds.set(mSplitLayout.getBounds1());
- outBottomOrRightBounds.set(mSplitLayout.getBounds2());
+ outTopOrLeftBounds.set(mSplitLayout.getTopLeftBounds());
+ outBottomOrRightBounds.set(mSplitLayout.getBottomRightBounds());
+ }
+
+ private void runForActiveStages(Consumer<StageTaskListener> consumer) {
+ mStageOrderOperator.getActiveStages().forEach(consumer);
+ }
+
+ private boolean runForActiveStagesAllMatch(Predicate<StageTaskListener> predicate) {
+ List<StageTaskListener> activeStages = mStageOrderOperator.getActiveStages();
+ return !activeStages.isEmpty() && activeStages.stream().allMatch(predicate);
}
@SplitPosition
@@ -1516,6 +1735,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private void addActivityOptions(Bundle opts, @Nullable StageTaskListener launchTarget) {
ActivityOptions options = ActivityOptions.fromBundle(opts);
if (launchTarget != null) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+ "addActivityOptions setting launch root for stage=%s",
+ stageTypeToString(launchTarget.getId()));
options.setLaunchRootTask(launchTarget.mRootTaskInfo.token);
}
// Put BAL flags to avoid activity start aborted. Otherwise, flows like shortcut to split
@@ -1559,8 +1781,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
listener.onSplitBoundsChanged(mSplitLayout.getRootBounds(), getMainStageBounds(),
getSideStageBounds());
}
- mSideStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_SIDE);
- mMainStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_MAIN);
+ if (enableFlexibleSplit()) {
+ // TODO(b/349828130) replace w/ stageID
+ mStageOrderOperator.getAllStages().forEach(
+ stage -> stage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_UNDEFINED)
+ );
+ } else {
+ mSideStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_SIDE);
+ mMainStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_MAIN);
+ }
}
private void sendOnStagePositionChanged() {
@@ -1584,17 +1813,25 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
boolean present, boolean visible) {
int stage;
if (present) {
- stage = stageListener == mSideStage ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN;
+ if (enableFlexibleSplit()) {
+ stage = stageListener.getId();
+ } else {
+ stage = stageListener == mSideStage ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN;
+ }
} else {
// No longer on any stage
stage = STAGE_TYPE_UNDEFINED;
}
- if (stage == STAGE_TYPE_MAIN) {
- mLogger.logMainStageAppChange(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
- mSplitLayout.isLeftRightSplit());
- } else if (stage == STAGE_TYPE_SIDE) {
- mLogger.logSideStageAppChange(getSideStagePosition(), mSideStage.getTopChildTaskUid(),
- mSplitLayout.isLeftRightSplit());
+ if (!enableFlexibleSplit()) {
+ if (stage == STAGE_TYPE_MAIN) {
+ mLogger.logMainStageAppChange(getMainStagePosition(),
+ mMainStage.getTopChildTaskUid(),
+ mSplitLayout.isLeftRightSplit());
+ } else if (stage == STAGE_TYPE_SIDE) {
+ mLogger.logSideStageAppChange(getSideStagePosition(),
+ mSideStage.getTopChildTaskUid(),
+ mSplitLayout.isLeftRightSplit());
+ }
}
if (present) {
updateRecentTasksSplitPair();
@@ -1620,19 +1857,40 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return;
}
mRecentTasks.ifPresent(recentTasks -> {
- Rect topLeftBounds = mSplitLayout.getBounds1();
- Rect bottomRightBounds = mSplitLayout.getBounds2();
- int mainStageTopTaskId = mMainStage.getTopVisibleChildTaskId();
- int sideStageTopTaskId = mSideStage.getTopVisibleChildTaskId();
+ Rect topLeftBounds = new Rect();
+ mSplitLayout.copyTopLeftBounds(topLeftBounds);
+ Rect bottomRightBounds = new Rect();
+ mSplitLayout.copyBottomRightBounds(bottomRightBounds);
+
+ int sideStageTopTaskId;
+ int mainStageTopTaskId;
+ if (enableFlexibleSplit()) {
+ List<StageTaskListener> activeStages = mStageOrderOperator.getActiveStages();
+ if (activeStages.size() != 2) {
+ sideStageTopTaskId = mainStageTopTaskId = INVALID_TASK_ID;
+ } else {
+ // doesn't matter which one we assign to? What matters is the order of 0 and 1?
+ mainStageTopTaskId = activeStages.get(0).getTopVisibleChildTaskId();
+ sideStageTopTaskId = activeStages.get(1).getTopVisibleChildTaskId();
+ }
+ } else {
+ mainStageTopTaskId = mMainStage.getTopVisibleChildTaskId();
+ sideStageTopTaskId= mSideStage.getTopVisibleChildTaskId();
+ }
boolean sideStageTopLeft = mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT;
int leftTopTaskId;
int rightBottomTaskId;
- if (sideStageTopLeft) {
- leftTopTaskId = sideStageTopTaskId;
- rightBottomTaskId = mainStageTopTaskId;
- } else {
+ if (enableFlexibleSplit()) {
leftTopTaskId = mainStageTopTaskId;
rightBottomTaskId = sideStageTopTaskId;
+ } else {
+ if (sideStageTopLeft) {
+ leftTopTaskId = sideStageTopTaskId;
+ rightBottomTaskId = mainStageTopTaskId;
+ } else {
+ leftTopTaskId = mainStageTopTaskId;
+ rightBottomTaskId = sideStageTopTaskId;
+ }
}
if (Flags.enableFlexibleTwoAppSplit()) {
@@ -1741,29 +1999,59 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@VisibleForTesting
@Override
public void onRootTaskAppeared() {
- ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRootTaskAppeared: rootTask=%s mainRoot=%b sideRoot=%b",
- mRootTaskInfo, mMainStage.mHasRootTask, mSideStage.mHasRootTask);
+ if (enableFlexibleSplit()) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRootTaskAppeared: rootTask=%s",
+ mRootTaskInfo);
+ mStageOrderOperator.getAllStages().forEach(stage -> {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+ " onRootStageAppeared stageId=%s hasRoot=%b",
+ stageTypeToString(stage.getId()), stage.mHasRootTask);
+ });
+ } else {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
+ "onRootTaskAppeared: rootTask=%s mainRoot=%b sideRoot=%b",
+ mRootTaskInfo, mMainStage.mHasRootTask, mSideStage.mHasRootTask);
+ }
+ boolean notAllStagesHaveRootTask;
+ if (enableFlexibleSplit()) {
+ notAllStagesHaveRootTask = mStageOrderOperator.getAllStages().stream()
+ .anyMatch((stage) -> !stage.mHasRootTask);
+ } else {
+ notAllStagesHaveRootTask = !mMainStage.mHasRootTask
+ || !mSideStage.mHasRootTask;
+ }
// Wait unit all root tasks appeared.
- if (mRootTaskInfo == null
- || !mMainStage.mHasRootTask
- || !mSideStage.mHasRootTask) {
+ if (mRootTaskInfo == null || notAllStagesHaveRootTask) {
return;
}
final WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.reparent(mMainStage.mRootTaskInfo.token, mRootTaskInfo.token, true);
- wct.reparent(mSideStage.mRootTaskInfo.token, mRootTaskInfo.token, true);
+ if (enableFlexibleSplit()) {
+ mStageOrderOperator.getAllStages().forEach(stage ->
+ wct.reparent(stage.mRootTaskInfo.token, mRootTaskInfo.token, true));
+ } else {
+ wct.reparent(mMainStage.mRootTaskInfo.token, mRootTaskInfo.token, true);
+ wct.reparent(mSideStage.mRootTaskInfo.token, mRootTaskInfo.token, true);
+ }
- // Make the stages adjacent to each other so they occlude what's behind them.
- wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
setRootForceTranslucent(true, wct);
- mSplitLayout.getInvisibleBounds(mTempRect1);
- wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
+ if (!enableFlexibleSplit()) {
+ //TODO(b/373709676) Need to figure out how adjacentRoots work for flex split
+
+ // Make the stages adjacent to each other so they occlude what's behind them.
+ wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
+ mSplitLayout.getInvisibleBounds(mTempRect1);
+ wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
+ }
mSyncQueue.queue(wct);
- mSyncQueue.runInSync(t -> {
- t.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.top);
- });
- mLaunchAdjacentController.setLaunchAdjacentRoot(mSideStage.mRootTaskInfo.token);
+ if (!enableFlexibleSplit()) {
+ mSyncQueue.runInSync(t -> {
+ t.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.top);
+ });
+ mLaunchAdjacentController.setLaunchAdjacentRoot(mSideStage.mRootTaskInfo.token);
+ } else {
+ // TODO(b/373709676) Need to figure out how adjacentRoots work for flex split
+ }
}
@Override
@@ -1958,15 +2246,21 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
@Override
- public void onSnappedToDismiss(boolean bottomOrRight, @ExitReason int exitReason) {
+ public void onSnappedToDismiss(boolean closedBottomRightStage, @ExitReason int exitReason) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onSnappedToDismiss: bottomOrRight=%b reason=%s",
- bottomOrRight, exitReasonToString(exitReason));
- final boolean mainStageToTop =
- bottomOrRight ? mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
+ closedBottomRightStage, exitReasonToString(exitReason));
+ boolean mainStageToTop =
+ closedBottomRightStage ? mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
: mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT;
- final StageTaskListener toTopStage = mainStageToTop ? mMainStage : mSideStage;
-
- final int dismissTop = mainStageToTop ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+ StageTaskListener toTopStage = mainStageToTop ? mMainStage : mSideStage;
+ int dismissTop = mainStageToTop ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+ if (enableFlexibleSplit()) {
+ toTopStage = mStageOrderOperator.getStageForLegacyPosition(closedBottomRightStage
+ ? SPLIT_POSITION_TOP_OR_LEFT
+ : SPLIT_POSITION_BOTTOM_OR_RIGHT,
+ false /*checkAllStagesIfNotActive*/);
+ dismissTop = toTopStage.getId();
+ }
final WindowContainerTransaction wct = new WindowContainerTransaction();
toTopStage.resetBounds(wct);
prepareExitSplitScreen(dismissTop, wct);
@@ -1998,8 +2292,21 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
updateSurfaceBounds(layout, t, shouldUseParallaxEffect);
getMainStageBounds(mTempRect1);
getSideStageBounds(mTempRect2);
- mMainStage.onResizing(mTempRect1, mTempRect2, t, offsetX, offsetY, mShowDecorImmediately);
- mSideStage.onResizing(mTempRect2, mTempRect1, t, offsetX, offsetY, mShowDecorImmediately);
+ if (enableFlexibleSplit()) {
+ StageTaskListener ltStage =
+ mStageOrderOperator.getStageForLegacyPosition(SPLIT_POSITION_TOP_OR_LEFT,
+ false /*checkAllStagesIfNotActive*/);
+ StageTaskListener brStage =
+ mStageOrderOperator.getStageForLegacyPosition(SPLIT_POSITION_BOTTOM_OR_RIGHT,
+ false /*checkAllStagesIfNotActive*/);
+ ltStage.onResizing(mTempRect1, mTempRect2, t, offsetX, offsetY, mShowDecorImmediately);
+ brStage.onResizing(mTempRect2, mTempRect1, t, offsetX, offsetY, mShowDecorImmediately);
+ } else {
+ mMainStage.onResizing(mTempRect1, mTempRect2, t, offsetX, offsetY,
+ mShowDecorImmediately);
+ mSideStage.onResizing(mTempRect2, mTempRect1, t, offsetX, offsetY,
+ mShowDecorImmediately);
+ }
t.apply();
mTransactionPool.release(t);
}
@@ -2015,19 +2322,33 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (!sizeChanged) {
// We still need to resize on decor for ensure all current status clear.
final SurfaceControl.Transaction t = mTransactionPool.acquire();
- mMainStage.onResized(t);
- mSideStage.onResized(t);
+ if (enableFlexibleSplit()) {
+ runForActiveStages(stage -> stage.onResized(t));
+ } else {
+ mMainStage.onResized(t);
+ mSideStage.onResized(t);
+ }
mTransactionPool.release(t);
return;
}
-
+ List<SplitDecorManager> decorManagers = new ArrayList<>();
+ SplitDecorManager mainDecor = null;
+ SplitDecorManager sideDecor = null;
+ if (enableFlexibleSplit()) {
+ decorManagers = mStageOrderOperator.getActiveStages().stream()
+ .map(StageTaskListener::getSplitDecorManager)
+ .toList();
+ } else {
+ mainDecor = mMainStage.getSplitDecorManager();
+ sideDecor = mSideStage.getSplitDecorManager();
+ }
sendOnBoundsChanged();
mSplitLayout.setDividerInteractive(false, false, "onSplitResizeStart");
mSplitTransitions.startResizeTransition(wct, this, (aborted) -> {
mSplitLayout.setDividerInteractive(true, false, "onSplitResizeConsumed");
}, (finishWct, t) -> {
mSplitLayout.setDividerInteractive(true, false, "onSplitResizeFinish");
- }, mMainStage.getSplitDecorManager(), mSideStage.getSplitDecorManager());
+ }, mainDecor, sideDecor, decorManagers);
if (Flags.enableFlexibleTwoAppSplit()) {
switch (layout.calculateCurrentSnapPosition()) {
@@ -2054,29 +2375,55 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
* @return true if stage bounds actually .
*/
private boolean updateWindowBounds(SplitLayout layout, WindowContainerTransaction wct) {
- final StageTaskListener topLeftStage =
- mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
- final StageTaskListener bottomRightStage =
- mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
+ final StageTaskListener topLeftStage;
+ final StageTaskListener bottomRightStage;
+ if (enableFlexibleSplit()) {
+ topLeftStage = mStageOrderOperator
+ .getStageForLegacyPosition(SPLIT_POSITION_TOP_OR_LEFT,
+ true /*checkAllStagesIfNotActive*/);
+ bottomRightStage = mStageOrderOperator
+ .getStageForLegacyPosition(SPLIT_POSITION_BOTTOM_OR_RIGHT,
+ true /*checkAllStagesIfNotActive*/);
+ } else {
+ topLeftStage = mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
+ ? mSideStage
+ : mMainStage;
+ bottomRightStage = mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
+ ? mMainStage
+ : mSideStage;
+ }
boolean updated = layout.applyTaskChanges(wct, topLeftStage.mRootTaskInfo,
bottomRightStage.mRootTaskInfo);
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "updateWindowBounds: topLeftStage=%s bottomRightStage=%s",
- layout.getBounds1(), layout.getBounds2());
+ layout.getTopLeftBounds(), layout.getBottomRightBounds());
return updated;
}
void updateSurfaceBounds(@Nullable SplitLayout layout, @NonNull SurfaceControl.Transaction t,
boolean applyResizingOffset) {
- final StageTaskListener topLeftStage =
- mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
- final StageTaskListener bottomRightStage =
- mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
+ final StageTaskListener topLeftStage;
+ final StageTaskListener bottomRightStage;
+ if (enableFlexibleSplit()) {
+ topLeftStage = mStageOrderOperator
+ .getStageForLegacyPosition(SPLIT_POSITION_TOP_OR_LEFT,
+ true /*checkAllStagesIfNotActive*/);
+ bottomRightStage = mStageOrderOperator
+ .getStageForLegacyPosition(SPLIT_POSITION_BOTTOM_OR_RIGHT,
+ true /*checkAllStagesIfNotActive*/);
+ } else {
+ topLeftStage = mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
+ ? mSideStage
+ : mMainStage;
+ bottomRightStage = mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
+ ? mMainStage
+ : mSideStage;
+ }
(layout != null ? layout : mSplitLayout).applySurfaceChanges(t, topLeftStage.mRootLeash,
bottomRightStage.mRootLeash, topLeftStage.mDimLayer, bottomRightStage.mDimLayer,
applyResizingOffset);
ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
"updateSurfaceBounds: topLeftStage=%s bottomRightStage=%s",
- layout.getBounds1(), layout.getBounds2());
+ layout.getTopLeftBounds(), layout.getBottomRightBounds());
}
@Override
@@ -2085,10 +2432,22 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return SPLIT_POSITION_UNDEFINED;
}
- if (mMainStage.containsToken(token)) {
- return getMainStagePosition();
- } else if (mSideStage.containsToken(token)) {
- return getSideStagePosition();
+ if (enableFlexibleSplit()) {
+ // We could migrate to/return the new INDEX enums here since most callers just care that
+ // this value isn't SPLIT_POSITION_UNDEFINED, but
+ // ImePositionProcessor#getImeTargetPosition actually uses the leftTop/bottomRight value
+ StageTaskListener stageForToken = mStageOrderOperator.getAllStages().stream()
+ .filter(stage -> stage.containsToken(token))
+ .findFirst().orElse(null);
+ return stageForToken == null
+ ? SPLIT_POSITION_UNDEFINED
+ : mStageOrderOperator.getLegacyPositionForStage(stageForToken);
+ } else {
+ if (mMainStage.containsToken(token)) {
+ return getMainStagePosition();
+ } else if (mSideStage.containsToken(token)) {
+ return getSideStagePosition();
+ }
}
return SPLIT_POSITION_UNDEFINED;
@@ -2185,27 +2544,49 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private Rect getSideStageBounds() {
return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
- ? mSplitLayout.getBounds1() : mSplitLayout.getBounds2();
+ ? mSplitLayout.getTopLeftBounds() : mSplitLayout.getBottomRightBounds();
}
private Rect getMainStageBounds() {
return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
- ? mSplitLayout.getBounds2() : mSplitLayout.getBounds1();
+ ? mSplitLayout.getBottomRightBounds() : mSplitLayout.getTopLeftBounds();
}
+ /**
+ * TODO(b/349828130) Currently the way this is being used is only to to get the bottomRight
+ * stage. Eventually we'll need to rename and for now we'll repurpose the method to return
+ * the bottomRight bounds under the flex split flag
+ */
private void getSideStageBounds(Rect rect) {
- if (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT) {
- mSplitLayout.getBounds1(rect);
+ if (enableFlexibleSplit()) {
+ // Split Layout doesn't actually keep track of the bounds based on the stage,
+ // it only knows that bounds1 is leftTop position and bounds2 is bottomRight position
+ // We'll then assume this method is to get bounds of bottomRight stage
+ mSplitLayout.copyBottomRightBounds(rect);
+ } else if (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT) {
+ mSplitLayout.copyTopLeftBounds(rect);
} else {
- mSplitLayout.getBounds2(rect);
+ mSplitLayout.copyBottomRightBounds(rect);
}
}
+ /**
+ * TODO(b/349828130) Currently the way this is being used is only to to get the leftTop
+ * stage. Eventually we'll need to rename and for now we'll repurpose the method to return
+ * the leftTop bounds under the flex split flag
+ */
private void getMainStageBounds(Rect rect) {
- if (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT) {
- mSplitLayout.getBounds2(rect);
+ if (enableFlexibleSplit()) {
+ // Split Layout doesn't actually keep track of the bounds based on the stage,
+ // it only knows that bounds1 is leftTop position and bounds2 is bottomRight position
+ // We'll then assume this method is to get bounds of topLeft stage
+ mSplitLayout.copyTopLeftBounds(rect);
} else {
- mSplitLayout.getBounds1(rect);
+ if (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT) {
+ mSplitLayout.copyBottomRightBounds(rect);
+ } else {
+ mSplitLayout.copyTopLeftBounds(rect);
+ }
}
}
@@ -2214,14 +2595,23 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
* this task (yet) so this can also be used to identify which stage to put a task into.
*/
private StageTaskListener getStageOfTask(ActivityManager.RunningTaskInfo taskInfo) {
- // TODO(b/184679596): Find a way to either include task-org information in the transition,
- // or synchronize task-org callbacks so we can use stage.containsTask
- if (mMainStage.mRootTaskInfo != null
- && taskInfo.parentTaskId == mMainStage.mRootTaskInfo.taskId) {
- return mMainStage;
- } else if (mSideStage.mRootTaskInfo != null
- && taskInfo.parentTaskId == mSideStage.mRootTaskInfo.taskId) {
- return mSideStage;
+ if (enableFlexibleSplit()) {
+ return mStageOrderOperator.getActiveStages().stream()
+ .filter((stage) -> stage.mRootTaskInfo != null &&
+ taskInfo.parentTaskId == stage.mRootTaskInfo.taskId
+ )
+ .findFirst()
+ .orElse(null);
+ } else {
+ // TODO(b/184679596): Find a way to either include task-org information in the
+ // transition, or synchronize task-org callbacks so we can use stage.containsTask
+ if (mMainStage.mRootTaskInfo != null
+ && taskInfo.parentTaskId == mMainStage.mRootTaskInfo.taskId) {
+ return mMainStage;
+ } else if (mSideStage.mRootTaskInfo != null
+ && taskInfo.parentTaskId == mSideStage.mRootTaskInfo.taskId) {
+ return mSideStage;
+ }
}
return null;
}
@@ -2229,7 +2619,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@StageType
private int getStageType(StageTaskListener stage) {
if (stage == null) return STAGE_TYPE_UNDEFINED;
- return stage == mMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+ if (enableFlexibleSplit()) {
+ return stage.getId();
+ } else {
+ return stage == mMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+ }
}
@Override
@@ -2276,11 +2670,17 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (isSplitActive()) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d split active",
request.getDebugId());
+ StageTaskListener primaryStage = enableFlexibleSplit()
+ ? mStageOrderOperator.getActiveStages().get(0)
+ : mMainStage;
+ StageTaskListener secondaryStage = enableFlexibleSplit()
+ ? mStageOrderOperator.getActiveStages().get(1)
+ : mSideStage;
// Try to handle everything while in split-screen, so return a WCT even if it's empty.
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " split is active so using split"
+ "Transition to handle request. triggerTask=%d type=%s mainChildren=%d"
+ " sideChildren=%d", triggerTask.taskId, transitTypeToString(type),
- mMainStage.getChildCount(), mSideStage.getChildCount());
+ primaryStage.getChildCount(), secondaryStage.getChildCount());
out = new WindowContainerTransaction();
if (stage != null) {
if (isClosingType(type) && stage.getChildCount() == 1) {
@@ -2320,11 +2720,21 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// the remote handler.
return null;
}
-
- if ((mMainStage.containsTask(triggerTask.taskId)
- && mMainStage.getChildCount() == 1)
- || (mSideStage.containsTask(triggerTask.taskId)
- && mSideStage.getChildCount() == 1)) {
+ boolean anyStageContainsSingleFullscreenTask;
+ if (enableFlexibleSplit()) {
+ anyStageContainsSingleFullscreenTask =
+ mStageOrderOperator.getActiveStages().stream()
+ .anyMatch(stageListener ->
+ stageListener.containsTask(triggerTask.taskId)
+ && stageListener.getChildCount() == 1);
+ } else {
+ anyStageContainsSingleFullscreenTask =
+ (mMainStage.containsTask(triggerTask.taskId)
+ && mMainStage.getChildCount() == 1)
+ || (mSideStage.containsTask(triggerTask.taskId)
+ && mSideStage.getChildCount() == 1);
+ }
+ if (anyStageContainsSingleFullscreenTask) {
// A splitting task is opening to fullscreen causes one side of the split empty,
// so appends operations to exit split.
prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, out);
@@ -2344,11 +2754,19 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// One of the cases above handled it
return out;
} else if (isSplitScreenVisible()) {
+ boolean allStagesHaveChildren;
+ if (enableFlexibleSplit()) {
+ allStagesHaveChildren = runForActiveStagesAllMatch(stageTaskListener ->
+ stageTaskListener.getChildCount() != 0);
+ } else {
+ allStagesHaveChildren = mMainStage.getChildCount() != 0
+ && mSideStage.getChildCount() != 0;
+ }
// If split is visible, only defer handling this transition if it's launching
// adjacent while there is already a split pair -- this may trigger PIP and
// that should be handled by the mixed handler.
final boolean deferTransition = requestHasLaunchAdjacentFlag(request)
- && mMainStage.getChildCount() != 0 && mSideStage.getChildCount() != 0;
+ && allStagesHaveChildren;
return !deferTransition ? out : null;
}
// Don't intercept the transition if we are not handling it as a part of one of the
@@ -2588,8 +3006,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
final ArraySet<StageTaskListener> dismissStages = record.getShouldDismissedStage();
- if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0
- || dismissStages.size() == 1) {
+ boolean anyStageHasNoChildren;
+ if (enableFlexibleSplit()) {
+ anyStageHasNoChildren = mStageOrderOperator.getActiveStages().stream()
+ .anyMatch(stage -> stage.getChildCount() == 0);
+ } else {
+ anyStageHasNoChildren = mMainStage.getChildCount() == 0
+ || mSideStage.getChildCount() == 0;
+ }
+ if (anyStageHasNoChildren || dismissStages.size() == 1) {
// If the size of dismissStages == 1, one of the task is closed without prepare
// pending transition, which could happen if all activities were finished after
// finish top activity in a task, so the trigger task is null when handleRequest.
@@ -2735,24 +3160,48 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
shouldAnimate = startPendingDismissAnimation(
dismiss, info, startTransaction, finishTransaction);
if (shouldAnimate && dismiss.mReason == EXIT_REASON_DRAG_DIVIDER) {
- final StageTaskListener toTopStage =
- dismiss.mDismissTop == STAGE_TYPE_MAIN ? mMainStage : mSideStage;
+ StageTaskListener toTopStage;
+ if (enableFlexibleSplit()) {
+ toTopStage = mStageOrderOperator.getAllStages().stream()
+ .filter(stage -> stage.getId() == dismiss.mDismissTop)
+ .findFirst().orElseThrow();
+ } else {
+ toTopStage = dismiss.mDismissTop == STAGE_TYPE_MAIN ? mMainStage : mSideStage;
+ }
mSplitTransitions.playDragDismissAnimation(transition, info, startTransaction,
finishTransaction, finishCallback, toTopStage.mRootTaskInfo.token,
toTopStage.getSplitDecorManager(), mRootTaskInfo.token);
return true;
}
} else if (mSplitTransitions.isPendingResize(transition)) {
+ Map<WindowContainerToken, SplitDecorManager> tokenDecorMap = new HashMap<>();
+ if (enableFlexibleSplit()) {
+ runForActiveStages(stageTaskListener ->
+ tokenDecorMap.put(stageTaskListener.mRootTaskInfo.getToken(),
+ stageTaskListener.getSplitDecorManager()));
+ } else {
+ tokenDecorMap.put(mMainStage.mRootTaskInfo.getToken(),
+ mMainStage.getSplitDecorManager());
+ tokenDecorMap.put(mSideStage.mRootTaskInfo.getToken(),
+ mSideStage.getSplitDecorManager());
+ }
mSplitTransitions.playResizeAnimation(transition, info, startTransaction,
- finishTransaction, finishCallback, mMainStage.mRootTaskInfo.token,
- mSideStage.mRootTaskInfo.token, mMainStage.getSplitDecorManager(),
- mSideStage.getSplitDecorManager());
+ finishTransaction, finishCallback, tokenDecorMap);
return true;
}
if (!shouldAnimate) return false;
+ WindowContainerToken mainToken;
+ WindowContainerToken sideToken;
+ if (enableFlexibleSplit()) {
+ mainToken = mStageOrderOperator.getActiveStages().get(0).mRootTaskInfo.token;
+ sideToken = mStageOrderOperator.getActiveStages().get(1).mRootTaskInfo.token;
+ } else {
+ mainToken = mMainStage.mRootTaskInfo.token;
+ sideToken = mSideStage.mRootTaskInfo.token;
+ }
mSplitTransitions.playAnimation(transition, info, startTransaction, finishTransaction,
- finishCallback, mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token,
+ finishCallback, mainToken, sideToken,
mRootTaskInfo.token);
return true;
}
@@ -2777,6 +3226,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// First, verify that we actually have opened apps in both splits.
TransitionInfo.Change mainChild = null;
TransitionInfo.Change sideChild = null;
+ StageTaskListener firstAppStage = null;
+ StageTaskListener secondAppStage = null;
final WindowContainerTransaction evictWct = new WindowContainerTransaction();
for (int iC = 0; iC < info.getChanges().size(); ++iC) {
final TransitionInfo.Change change = info.getChanges().get(iC);
@@ -2785,14 +3236,19 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (mPausingTasks.contains(taskInfo.taskId)) {
continue;
}
- final @StageType int stageType = getStageType(getStageOfTask(taskInfo));
- if (mainChild == null && stageType == STAGE_TYPE_MAIN
+ StageTaskListener stage = getStageOfTask(taskInfo);
+ final @StageType int stageType = getStageType(stage);
+ if (mainChild == null
+ && stageType == (enableFlexibleSplit() ? STAGE_TYPE_A : STAGE_TYPE_MAIN)
&& (isOpeningType(change.getMode()) || change.getMode() == TRANSIT_CHANGE)) {
// Includes TRANSIT_CHANGE to cover reparenting top-most task to split.
mainChild = change;
- } else if (sideChild == null && stageType == STAGE_TYPE_SIDE
+ firstAppStage = getStageOfTask(taskInfo);
+ } else if (sideChild == null
+ && stageType == (enableFlexibleSplit() ? STAGE_TYPE_B : STAGE_TYPE_SIDE)
&& (isOpeningType(change.getMode()) || change.getMode() == TRANSIT_CHANGE)) {
sideChild = change;
+ secondAppStage = stage;
} else if (stageType != STAGE_TYPE_UNDEFINED && change.getMode() == TRANSIT_TO_BACK) {
// Collect all to back task's and evict them when transition finished.
evictWct.reparent(taskInfo.token, null /* parent */, false /* onTop */);
@@ -2848,9 +3304,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// TODO(b/184679596): Find a way to either include task-org information in
// the transition, or synchronize task-org callbacks.
final boolean mainNotContainOpenTask =
- mainChild != null && !mMainStage.containsTask(mainChild.getTaskInfo().taskId);
+ mainChild != null && !firstAppStage.containsTask(mainChild.getTaskInfo().taskId);
final boolean sideNotContainOpenTask =
- sideChild != null && !mSideStage.containsTask(sideChild.getTaskInfo().taskId);
+ sideChild != null && !secondAppStage.containsTask(sideChild.getTaskInfo().taskId);
if (mainNotContainOpenTask) {
Log.w(TAG, "Expected onTaskAppeared on " + mMainStage
+ " to have been called with " + mainChild.getTaskInfo().taskId
@@ -2863,6 +3319,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
final TransitionInfo.Change finalMainChild = mainChild;
final TransitionInfo.Change finalSideChild = sideChild;
+ final StageTaskListener finalFirstAppStage = firstAppStage;
+ final StageTaskListener finalSecondAppStage = secondAppStage;
enterTransition.setFinishedCallback((callbackWct, callbackT) -> {
if (!enterTransition.mResizeAnim) {
// If resizing, we'll call notify at the end of the resizing animation (below)
@@ -2870,16 +3328,18 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
if (finalMainChild != null) {
if (!mainNotContainOpenTask) {
- mMainStage.evictOtherChildren(callbackWct, finalMainChild.getTaskInfo().taskId);
+ finalFirstAppStage.evictOtherChildren(callbackWct,
+ finalMainChild.getTaskInfo().taskId);
} else {
- mMainStage.evictInvisibleChildren(callbackWct);
+ finalFirstAppStage.evictInvisibleChildren(callbackWct);
}
}
if (finalSideChild != null) {
if (!sideNotContainOpenTask) {
- mSideStage.evictOtherChildren(callbackWct, finalSideChild.getTaskInfo().taskId);
+ finalSecondAppStage.evictOtherChildren(callbackWct,
+ finalSideChild.getTaskInfo().taskId);
} else {
- mSideStage.evictInvisibleChildren(callbackWct);
+ finalSecondAppStage.evictInvisibleChildren(callbackWct);
}
}
if (!evictWct.isEmpty()) {
@@ -2961,8 +3421,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
public void onPipExpandToSplit(WindowContainerTransaction wct,
ActivityManager.RunningTaskInfo taskInfo) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onPipExpandToSplit: task=%s", taskInfo);
+ // TODO(b/349828130) currently pass in index_undefined until we can revisit these
+ // flex split + pip interactions in the future
prepareEnterSplitScreen(wct, taskInfo, getActivateSplitPosition(taskInfo),
- false /*resizeAnim*/);
+ false /*resizeAnim*/, SPLIT_INDEX_UNDEFINED);
if (!isSplitScreenVisible() || mSplitRequest == null) {
return;
@@ -3067,13 +3529,28 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// Wait until after animation to update divider
// Reset crops so they don't interfere with subsequent launches
- t.setCrop(mMainStage.mRootLeash, null);
- t.setCrop(mSideStage.mRootLeash, null);
+ if (enableFlexibleSplit()) {
+ runForActiveStages(stage -> t.setCrop(stage.mRootLeash, null /*crop*/));
+ } else {
+ t.setCrop(mMainStage.mRootLeash, null);
+ t.setCrop(mSideStage.mRootLeash, null);
+ }
// Hide the non-top stage and set the top one to the fullscreen position.
if (toStage != STAGE_TYPE_UNDEFINED) {
- t.hide(toStage == STAGE_TYPE_MAIN ? mSideStage.mRootLeash : mMainStage.mRootLeash);
- t.setPosition(toStage == STAGE_TYPE_MAIN
- ? mMainStage.mRootLeash : mSideStage.mRootLeash, 0, 0);
+ if (enableFlexibleSplit()) {
+ StageTaskListener stageToKeep = mStageOrderOperator.getAllStages().stream()
+ .filter(stage -> stage.getId() == toStage)
+ .findFirst().orElseThrow();
+ List<StageTaskListener> stagesToHide = mStageOrderOperator.getAllStages().stream()
+ .filter(stage -> stage.getId() != toStage)
+ .toList();
+ stagesToHide.forEach(stage -> t.hide(stage.mRootLeash));
+ t.setPosition(stageToKeep.mRootLeash, 0, 0);
+ } else {
+ t.hide(toStage == STAGE_TYPE_MAIN ? mSideStage.mRootLeash : mMainStage.mRootLeash);
+ t.setPosition(toStage == STAGE_TYPE_MAIN
+ ? mMainStage.mRootLeash : mSideStage.mRootLeash, 0, 0);
+ }
} else {
for (int i = dismissingTasks.keySet().size() - 1; i >= 0; --i) {
finishT.hide(dismissingTasks.valueAt(i));
@@ -3088,8 +3565,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// Hide divider and dim layer on transition finished.
setDividerVisibility(false, t);
- finishT.hide(mMainStage.mDimLayer);
- finishT.hide(mSideStage.mDimLayer);
+ if (enableFlexibleSplit()) {
+ runForActiveStages(stage -> finishT.hide(stage.mRootLeash));
+ } else {
+ finishT.hide(mMainStage.mDimLayer);
+ finishT.hide(mSideStage.mDimLayer);
+ }
}
private boolean startPendingDismissAnimation(
@@ -3110,8 +3591,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return false;
}
dismissTransition.setFinishedCallback((callbackWct, callbackT) -> {
- mMainStage.getSplitDecorManager().release(callbackT);
- mSideStage.getSplitDecorManager().release(callbackT);
+ if (enableFlexibleSplit()) {
+ runForActiveStages(stage -> stage.getSplitDecorManager().release(callbackT));
+ } else {
+ mMainStage.getSplitDecorManager().release(callbackT);
+ mSideStage.getSplitDecorManager().release(callbackT);
+ }
callbackWct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, false);
});
return true;
@@ -3128,8 +3613,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (TransitionUtil.isClosingType(change.getMode())
&& change.getTaskInfo() != null) {
final int taskId = change.getTaskInfo().taskId;
- if (mMainStage.getTopVisibleChildTaskId() == taskId
- || mSideStage.getTopVisibleChildTaskId() == taskId) {
+ boolean anyStagesHaveTask;
+ if (enableFlexibleSplit()) {
+ anyStagesHaveTask = mStageOrderOperator.getActiveStages().stream()
+ .anyMatch(stage -> stage.getTopVisibleChildTaskId() == taskId);
+ } else {
+ anyStagesHaveTask = mMainStage.getTopVisibleChildTaskId() == taskId
+ || mSideStage.getTopVisibleChildTaskId() == taskId;
+ }
+ if (anyStagesHaveTask) {
mPausingTasks.add(taskId);
}
}
@@ -3161,9 +3653,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final WindowContainerTransaction.HierarchyOp op =
finishWct.getHierarchyOps().get(i);
final IBinder container = op.getContainer();
+ boolean anyStageContainsContainer;
+ if (enableFlexibleSplit()) {
+ anyStageContainsContainer = mStageOrderOperator.getActiveStages().stream()
+ .anyMatch(stage -> stage.containsContainer(container));
+ } else {
+ anyStageContainsContainer = mMainStage.containsContainer(container)
+ || mSideStage.containsContainer(container);
+ }
if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop()
- && (mMainStage.containsContainer(container)
- || mSideStage.containsContainer(container))) {
+ && anyStageContainsContainer) {
updateSurfaceBounds(mSplitLayout, finishT,
false /* applyResizingOffset */);
finishT.reparent(mSplitLayout.getDividerLeash(), mRootTaskLeash);
@@ -3190,10 +3689,18 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// user entering recents.
for (int i = mPausingTasks.size() - 1; i >= 0; --i) {
final int taskId = mPausingTasks.get(i);
- if (mMainStage.containsTask(taskId)) {
- mMainStage.evictChild(finishWct, taskId, "recentsPairToPair");
- } else if (mSideStage.containsTask(taskId)) {
- mSideStage.evictChild(finishWct, taskId, "recentsPairToPair");
+ if (enableFlexibleSplit()) {
+ mStageOrderOperator.getActiveStages().stream()
+ .filter(stage -> stage.containsTask(taskId))
+ .findFirst()
+ .ifPresent(stageToEvict ->
+ stageToEvict.evictChild(finishWct, taskId, "recentsPairToPair"));
+ } else {
+ if (mMainStage.containsTask(taskId)) {
+ mMainStage.evictChild(finishWct, taskId, "recentsPairToPair");
+ } else if (mSideStage.containsTask(taskId)) {
+ mSideStage.evictChild(finishWct, taskId, "recentsPairToPair");
+ }
}
}
// If pending enter hasn't consumed, the mix handler will invoke start pending
@@ -3256,8 +3763,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
*/
private void setSplitsVisible(boolean visible) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setSplitsVisible: visible=%b", visible);
- mMainStage.mVisible = mSideStage.mVisible = visible;
- mMainStage.mHasChildren = mSideStage.mHasChildren = visible;
+ if (enableFlexibleSplit()) {
+ runForActiveStages(stage -> {
+ stage.mVisible = visible;
+ stage.mHasChildren = visible;
+ });
+ } else {
+ mMainStage.mVisible = mSideStage.mVisible = visible;
+ mMainStage.mHasChildren = mSideStage.mHasChildren = visible;
+ }
}
/**
@@ -3300,6 +3814,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
* executed.
*/
private void logExitToStage(@ExitReason int exitReason, boolean toMainStage) {
+ if (enableFlexibleSplit()) {
+ // TODO(b/374825718) update logging for 2+ apps
+ return;
+ }
mLogger.logExit(exitReason,
toMainStage ? getMainStagePosition() : SPLIT_POSITION_UNDEFINED,
toMainStage ? mMainStage.getTopChildTaskUid() : 0 /* mainStageUid */,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageOrderOperator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageOrderOperator.kt
new file mode 100644
index 000000000000..b7b3c9b62a58
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageOrderOperator.kt
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.splitscreen
+
+import android.content.Context
+import com.android.internal.protolog.ProtoLog
+import com.android.launcher3.icons.IconProvider
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.protolog.ShellProtoLogGroup
+import com.android.wm.shell.shared.split.SplitScreenConstants
+import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_NONE
+import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_0
+import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_1
+import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_2
+import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
+import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
+import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED
+import com.android.wm.shell.shared.split.SplitScreenConstants.SnapPosition
+import com.android.wm.shell.shared.split.SplitScreenConstants.SplitIndex
+import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition
+import com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_A
+import com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_B
+import com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_C
+import com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString
+import com.android.wm.shell.windowdecor.WindowDecorViewModel
+import java.util.Optional
+
+/**
+ * Responsible for creating [StageTaskListener]s and maintaining their ordering on screen.
+ * Must be notified whenever stages positions change via swapping or starting/ending tasks
+ */
+class StageOrderOperator (
+ context: Context,
+ taskOrganizer: ShellTaskOrganizer,
+ displayId: Int,
+ stageCallbacks: StageTaskListener.StageListenerCallbacks,
+ syncQueue: SyncTransactionQueue,
+ iconProvider: IconProvider,
+ windowDecorViewModel: Optional<WindowDecorViewModel>
+ ) {
+
+ private val MAX_STAGES = 3
+ /**
+ * This somewhat acts as a replacement to stageTypes in the intermediary, so we want to start
+ * it after the @StageType constant values just to be safe and avoid potentially subtle bugs.
+ */
+ private var stageIds = listOf(STAGE_TYPE_A, STAGE_TYPE_B, STAGE_TYPE_C)
+
+ /**
+ * Active Stages, this list represent the current, ordered list of stages that are
+ * currently visible to the user. This map should be empty if the user is currently
+ * not in split screen. Note that this is different than if split screen is visible, which
+ * is determined by [StageListenerImpl.mVisible].
+ * Split stages can be active and in the background
+ */
+ val activeStages = mutableListOf<StageTaskListener>()
+ val allStages = mutableListOf<StageTaskListener>()
+ var isActive: Boolean = false
+ var isVisible: Boolean = false
+ @SnapPosition private var currentLayout: Int = SNAP_TO_NONE
+
+ init {
+ for(i in 0 until MAX_STAGES) {
+ allStages.add(StageTaskListener(context,
+ taskOrganizer,
+ displayId,
+ stageCallbacks,
+ syncQueue,
+ iconProvider,
+ windowDecorViewModel,
+ stageIds[i])
+ )
+ }
+ }
+
+ /**
+ * Updates internal state to keep record of "active" stages. Note that this does NOT call
+ * [StageTaskListener.activate] on the stages.
+ */
+ fun onEnteringSplit(@SnapPosition goingToLayout: Int) {
+ if (goingToLayout == currentLayout) {
+ // Add protolog here. Return for now, but maybe we want to handle swap case, TBD
+ return
+ }
+ val freeStages: List<StageTaskListener> =
+ allStages.filterNot { activeStages.contains(it) }
+ when(goingToLayout) {
+ SplitScreenConstants.SNAP_TO_2_50_50 -> {
+ if (activeStages.size < 2) {
+ // take from allStages and add into activeStages
+ for (i in 0 until (2 - activeStages.size)) {
+ val stage = freeStages[i]
+ activeStages.add(stage)
+ }
+ }
+ }
+ }
+ ProtoLog.d(
+ ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Activated stages: %d ids=%s",
+ activeStages.size,
+ activeStages.joinToString(",") { stageTypeToString(it.id) }
+ )
+ isActive = true
+ }
+
+ fun onExitingSplit() {
+ activeStages.clear()
+ isActive = false
+ }
+
+ /**
+ * Given a legacy [SplitPosition] returns one of the stages from the actives stages.
+ * If there are no active stages and [checkAllStagesIfNotActive] is not true, then will return
+ * null
+ */
+ fun getStageForLegacyPosition(@SplitPosition position: Int,
+ checkAllStagesIfNotActive : Boolean = false) :
+ StageTaskListener? {
+ if (activeStages.size != 2 && !checkAllStagesIfNotActive) {
+ return null
+ }
+ val listToCheck = if (activeStages.isEmpty() and checkAllStagesIfNotActive)
+ allStages else
+ activeStages
+ if (position == SPLIT_POSITION_TOP_OR_LEFT) {
+ return listToCheck[0]
+ } else if (position == SPLIT_POSITION_BOTTOM_OR_RIGHT) {
+ return listToCheck[1]
+ } else {
+ throw IllegalArgumentException("No stage for invalid position")
+ }
+ }
+
+ /**
+ * Returns a legacy split position for the given stage. If no stages are active then this will
+ * return [SPLIT_POSITION_UNDEFINED]
+ */
+ @SplitPosition
+ fun getLegacyPositionForStage(stage: StageTaskListener) : Int {
+ if (allStages[0] == stage) {
+ return SPLIT_POSITION_TOP_OR_LEFT
+ } else if (allStages[1] == stage) {
+ return SPLIT_POSITION_BOTTOM_OR_RIGHT
+ } else {
+ return SPLIT_POSITION_UNDEFINED
+ }
+ }
+
+ /**
+ * Returns the stageId from a given splitIndex. This will default to checking from all stages if
+ * [isActive] is false, otherwise will only check active stages.
+ */
+ fun getStageForIndex(@SplitIndex splitIndex: Int) : StageTaskListener {
+ // Probably should do a check for index to be w/in the bounds of the current split layout
+ // that we're currently in
+ val listToCheck = if (isActive) activeStages else allStages
+ if (splitIndex == SPLIT_INDEX_0) {
+ return listToCheck[0]
+ } else if (splitIndex == SPLIT_INDEX_1) {
+ return listToCheck[1]
+ } else if (splitIndex == SPLIT_INDEX_2) {
+ return listToCheck[2]
+ } else {
+ // Though I guess what if we're adding to the end? Maybe that indexing needs to be
+ // resolved elsewhere
+ throw IllegalStateException("No stage for the given splitIndex")
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 08cdfdb3b5a9..4a37169add36 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -22,10 +22,12 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static com.android.wm.shell.Flags.enableFlexibleSplit;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
import static com.android.wm.shell.shared.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
import static com.android.wm.shell.shared.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
import static com.android.wm.shell.shared.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE;
+import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString;
import android.annotation.CallSuper;
import android.annotation.Nullable;
@@ -72,6 +74,8 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
// No current way to enforce this but if enableFlexibleSplit() is enabled, then only 1 of the
// stages should have this be set/being used
private boolean mIsActive;
+ /** Unique identifier for this state, > 0 */
+ @StageType private final int mId;
/** Callback interface for listening to changes in a split-screen stage. */
public interface StageListenerCallbacks {
void onRootTaskAppeared();
@@ -110,13 +114,14 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
StageTaskListener(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
IconProvider iconProvider,
- Optional<WindowDecorViewModel> windowDecorViewModel) {
+ Optional<WindowDecorViewModel> windowDecorViewModel, int id) {
mContext = context;
mCallbacks = callbacks;
mSyncQueue = syncQueue;
mIconProvider = iconProvider;
mWindowDecorViewModel = windowDecorViewModel;
taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this);
+ mId = id;
}
int getChildCount() {
@@ -161,6 +166,11 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
return contains(t -> t.isFocused);
}
+ @StageType
+ int getId() {
+ return mId;
+ }
+
private boolean contains(Predicate<ActivityManager.RunningTaskInfo> predicate) {
if (mRootTaskInfo != null && predicate.test(mRootTaskInfo)) {
return true;
@@ -197,10 +207,10 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
@CallSuper
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskAppeared: taskId=%d taskParent=%d rootTask=%d "
- + "taskActivity=%s",
+ + "stageId=%s taskActivity=%s",
taskInfo.taskId, taskInfo.parentTaskId,
mRootTaskInfo != null ? mRootTaskInfo.taskId : -1,
- taskInfo.baseActivity);
+ stageTypeToString(mId), taskInfo.baseActivity);
if (mRootTaskInfo == null) {
mRootLeash = leash;
mRootTaskInfo = taskInfo;
@@ -230,8 +240,9 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
@Override
@CallSuper
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
- ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskInfoChanged: taskId=%d taskAct=%s",
- taskInfo.taskId, taskInfo.baseActivity);
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskInfoChanged: taskId=%d taskAct=%s "
+ + "stageId=%s",
+ taskInfo.taskId, taskInfo.baseActivity, stageTypeToString(mId));
mWindowDecorViewModel.ifPresent(viewModel -> viewModel.onTaskInfoChanged(taskInfo));
if (mRootTaskInfo.taskId == taskInfo.taskId) {
mRootTaskInfo = taskInfo;
@@ -261,7 +272,8 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
@Override
@CallSuper
public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
- ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskVanished: task=%d", taskInfo.taskId);
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskVanished: task=%d stageId=%s",
+ taskInfo.taskId, stageTypeToString(mId));
final int taskId = taskInfo.taskId;
mWindowDecorViewModel.ifPresent(vm -> vm.onTaskVanished(taskInfo));
if (mRootTaskInfo.taskId == taskId) {
@@ -466,14 +478,17 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
}
void activate(WindowContainerTransaction wct, boolean includingTopTask) {
- if (mIsActive) return;
- ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "activate: includingTopTask=%b",
- includingTopTask);
+ if (mIsActive && !enableFlexibleSplit()) return;
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "activate: includingTopTask=%b stage=%s",
+ includingTopTask, stageTypeToString(mId));
if (includingTopTask) {
reparentTopTask(wct);
}
+ if (enableFlexibleSplit()) {
+ return;
+ }
mIsActive = true;
}
@@ -481,11 +496,14 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
deactivate(wct, false /* toTop */);
}
- void deactivate(WindowContainerTransaction wct, boolean toTop) {
- if (!mIsActive) return;
- ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "deactivate: toTop=%b rootTaskInfo=%s",
- toTop, mRootTaskInfo);
- mIsActive = false;
+ void deactivate(WindowContainerTransaction wct, boolean reparentTasksToTop) {
+ if (!mIsActive && !enableFlexibleSplit()) return;
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "deactivate: reparentTasksToTop=%b "
+ + "rootTaskInfo=%s stage=%s",
+ reparentTasksToTop, mRootTaskInfo, stageTypeToString(mId));
+ if (!enableFlexibleSplit()) {
+ mIsActive = false;
+ }
if (mRootTaskInfo == null) return;
final WindowContainerToken rootToken = mRootTaskInfo.token;
@@ -494,14 +512,15 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
null /* newParent */,
null /* windowingModes */,
null /* activityTypes */,
- toTop);
+ reparentTasksToTop);
}
// --------
- // Previously only used in SideStage
+ // Previously only used in SideStage. With flexible split this is called for all stages
boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) {
- ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "remove all side stage tasks: childCount=%d toTop=%b",
- mChildrenTaskInfo.size(), toTop);
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "remove all side stage tasks: childCount=%d toTop=%b "
+ + " stageI=%s",
+ mChildrenTaskInfo.size(), toTop, stageTypeToString(mId));
if (mChildrenTaskInfo.size() == 0) return false;
wct.reparentTasks(
mRootTaskInfo.token,
@@ -522,6 +541,15 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
}
@Override
+ public String toString() {
+ return "mId: " + stageTypeToString(mId)
+ + " mVisible: " + mVisible
+ + " mActive: " + mIsActive
+ + " mHasRootTask: " + mHasRootTask
+ + " childSize: " + mChildrenTaskInfo.size();
+ }
+
+ @Override
@CallSuper
public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 17483dd68632..92d1f9c26bbc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -370,7 +370,8 @@ public class DefaultMixedHandler implements MixedTransitionHandler,
if (mRecentsHandler != null) {
if (mSplitHandler.isSplitScreenVisible()) {
return this::setRecentsTransitionDuringSplit;
- } else if (mKeyguardHandler.isKeyguardShowing()) {
+ } else if (mKeyguardHandler.isKeyguardShowing()
+ && !mKeyguardHandler.isKeyguardAnimating()) {
return this::setRecentsTransitionDuringKeyguard;
} else if (mDesktopTasksController != null
// Check on the default display. Recents/gesture nav is only available there
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
index fd4d568326d0..8cdbe26a2c76 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
@@ -117,6 +117,11 @@ class RecentsMixedTransition extends DefaultMixedHandler.MixedTransition {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Mixed transition for Recents during"
+ " Keyguard #%d", info.getDebugId());
+ if (!mKeyguardHandler.isKeyguardShowing() || mKeyguardHandler.isKeyguardAnimating()) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Cancel mixed transition because "
+ + "keyguard state was changed #%d", info.getDebugId());
+ return false;
+ }
if (mInfo == null) {
mInfo = info;
mFinishT = finishTransaction;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
index d28287da83b6..32f3cd820421 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
@@ -334,8 +334,8 @@ public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator,
// Sides adjacent to split bar or task bar are not be animated.
Insets margins;
- final boolean isLandscape = mRootStageBounds.width() > mRootStageBounds.height();
- if (isLandscape) { // Left and right splits.
+ final boolean isLeftRightSplit = mSplitScreenController.get().get().isLeftRightSplit();
+ if (isLeftRightSplit) {
margins = getLandscapeMargins(margin, taskbarExpanded);
} else { // Top and bottom splits.
margins = getPortraitMargins(margin, taskbarExpanded);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
index 4bb1e7b6cc05..11a7cf8da8d8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
@@ -70,6 +70,7 @@ import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHo
import com.android.wm.shell.windowdecor.common.DecorThemeUtil
import com.android.wm.shell.windowdecor.common.OPACITY_12
import com.android.wm.shell.windowdecor.common.OPACITY_40
+import com.android.wm.shell.windowdecor.common.OPACITY_60
import com.android.wm.shell.windowdecor.common.withAlpha
import java.util.function.Supplier
@@ -310,8 +311,6 @@ class MaximizeMenu(
.desktop_mode_maximize_menu_immersive_button_fill_padding)
private val maximizeFillPaddingDefault = context.resources.getDimensionPixelSize(R.dimen
.desktop_mode_maximize_menu_snap_and_maximize_buttons_fill_padding)
- private val maximizeFillPaddingBottom = context.resources.getDimensionPixelSize(R.dimen
- .desktop_mode_maximize_menu_snap_and_maximize_buttons_fill_padding_bottom)
private val maximizeRestoreFillPaddingVertical = context.resources.getDimensionPixelSize(
R.dimen.desktop_mode_maximize_menu_restore_button_fill_vertical_padding)
private val maximizeRestoreFillPaddingHorizontal = context.resources.getDimensionPixelSize(
@@ -320,7 +319,7 @@ class MaximizeMenu(
maximizeFillPaddingDefault,
maximizeFillPaddingDefault,
maximizeFillPaddingDefault,
- maximizeFillPaddingBottom
+ maximizeFillPaddingDefault
)
private val maximizeRestoreFillPaddingRect = Rect(
maximizeRestoreFillPaddingHorizontal,
@@ -684,7 +683,7 @@ class MaximizeMenu(
inactiveSnapSideColor = colorScheme.outlineVariant.toArgb(),
semiActiveSnapSideColor = colorScheme.primary.toArgb().withAlpha(OPACITY_40),
activeSnapSideColor = colorScheme.primary.toArgb(),
- inactiveStrokeColor = colorScheme.outlineVariant.toArgb(),
+ inactiveStrokeColor = colorScheme.outlineVariant.toArgb().withAlpha(OPACITY_60),
activeStrokeColor = colorScheme.primary.toArgb(),
inactiveBackgroundColor = menuBackgroundColor,
activeBackgroundColor = colorScheme.primary.toArgb().withAlpha(OPACITY_12)
@@ -753,7 +752,8 @@ class MaximizeMenu(
val activeStrokeAndFill = colorScheme.primary.toArgb()
val activeBackground = colorScheme.primary.toArgb().withAlpha(OPACITY_12)
val activeDrawable = createMaximizeOrImmersiveButtonDrawable(
- strokeAndFillColor = activeStrokeAndFill,
+ strokeColor = activeStrokeAndFill,
+ fillColor = activeStrokeAndFill,
backgroundColor = activeBackground,
// Add a mask with the menu background's color because the active background color is
// semi transparent, otherwise the transparency will reveal the stroke/fill color
@@ -770,7 +770,8 @@ class MaximizeMenu(
addState(
StateSet.WILD_CARD,
createMaximizeOrImmersiveButtonDrawable(
- strokeAndFillColor = colorScheme.outlineVariant.toArgb(),
+ strokeColor = colorScheme.outlineVariant.toArgb().withAlpha(OPACITY_60),
+ fillColor = colorScheme.outlineVariant.toArgb(),
backgroundColor = colorScheme.surfaceContainerLow.toArgb(),
backgroundMask = null, // not needed because the bg color is fully opaque
fillPadding = fillPadding,
@@ -780,7 +781,8 @@ class MaximizeMenu(
}
private fun createMaximizeOrImmersiveButtonDrawable(
- @ColorInt strokeAndFillColor: Int,
+ @ColorInt strokeColor: Int,
+ @ColorInt fillColor: Int,
@ColorInt backgroundColor: Int,
@ColorInt backgroundMask: Int?,
fillPadding: Rect,
@@ -794,7 +796,7 @@ class MaximizeMenu(
null /* inset */,
null /* innerRadii */
)
- paint.color = strokeAndFillColor
+ paint.color = strokeColor
paint.style = Paint.Style.FILL
})
// Second layer, a mask for the next (background) layer if needed because of
@@ -829,7 +831,7 @@ class MaximizeMenu(
null /* inset */,
null /* innerRadii */
)
- paint.color = strokeAndFillColor
+ paint.color = fillColor
paint.style = Paint.Style.FILL
})
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/ThemeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/ThemeUtils.kt
index f7cfbfa88485..c5057aa3cc18 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/ThemeUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/ThemeUtils.kt
@@ -52,6 +52,7 @@ const val OPACITY_12 = 31
const val OPACITY_15 = 38
const val OPACITY_40 = 102
const val OPACITY_55 = 140
+const val OPACITY_60 = 153
const val OPACITY_65 = 166
/**
diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS
index 65e50f86e8fe..19829e7e5677 100644
--- a/libs/WindowManager/Shell/tests/OWNERS
+++ b/libs/WindowManager/Shell/tests/OWNERS
@@ -6,6 +6,7 @@ pablogamito@google.com
lbill@google.com
madym@google.com
hwwang@google.com
+gabiyev@google.com
chenghsiuchang@google.com
atsjenk@google.com
jorgegil@google.com
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MinimizeAutoPipAppWindowTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MinimizeAutoPipAppWindowTest.kt
new file mode 100644
index 000000000000..48befc06b9c5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MinimizeAutoPipAppWindowTest.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.scenarios.MinimizeAutoPipAppWindow
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/* Functional test for [MinimizeAutoPipAppWindow]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+class MinimizeAutoPipAppWindowTest : MinimizeAutoPipAppWindow()
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt
index 0f546cdf97c5..8d04749d76a5 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt
@@ -49,7 +49,8 @@ constructor(
@Test
open fun enterDesktopWithDrag() {
- testApp.enterDesktopModeWithDrag(wmHelper, device)
+ // By default this method uses drag to desktop
+ testApp.enterDesktopMode(wmHelper, device)
}
@After
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAutoPipAppWindow.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAutoPipAppWindow.kt
new file mode 100644
index 000000000000..d6c3266e915c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAutoPipAppWindow.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.scenarios
+
+import android.app.Instrumentation
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.PipAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+/** Base scenario test for minimizing the app entering pip on leave automatically */
+@Ignore("Test Base Class")
+abstract class MinimizeAutoPipAppWindow {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+ private val pipApp = PipAppHelper(instrumentation)
+ private val pipAppDesktopMode = DesktopModeAppHelper(pipApp)
+
+ @Rule
+ @JvmField
+ val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, Rotation.ROTATION_0)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ Assume.assumeTrue(Flags.enableMinimizeButton())
+ testApp.enterDesktopMode(wmHelper, device)
+ pipApp.launchViaIntent(wmHelper)
+ pipApp.enableAutoEnterForPipActivity()
+ }
+
+ @Test
+ open fun minimizePipAppWindow() {
+ pipAppDesktopMode.minimizeDesktopApp(wmHelper, device, isPip = true)
+ }
+
+ @After
+ fun teardown() {
+ pipApp.exit(wmHelper)
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeWindowOnAppOpen.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeWindowOnAppOpen.kt
index 7987f7ec59fa..a246326f1137 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeWindowOnAppOpen.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeWindowOnAppOpen.kt
@@ -23,12 +23,10 @@ import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
-import com.android.server.wm.flicker.helpers.ImeAppHelper
-import com.android.server.wm.flicker.helpers.LetterboxAppHelper
import com.android.server.wm.flicker.helpers.MailAppHelper
-import com.android.server.wm.flicker.helpers.NewTasksAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.window.flags.Flags
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import org.junit.After
import org.junit.Assume
import org.junit.Before
@@ -51,32 +49,30 @@ open class MinimizeWindowOnAppOpen()
private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
private val mailApp = DesktopModeAppHelper(MailAppHelper(instrumentation))
- private val newTasksApp = DesktopModeAppHelper(NewTasksAppHelper(instrumentation))
- private val imeApp = DesktopModeAppHelper(ImeAppHelper(instrumentation))
- private val letterboxAppHelper = DesktopModeAppHelper(LetterboxAppHelper(instrumentation))
+
+ private val maxNum = DesktopModeStatus.getMaxTaskLimit(instrumentation.context)
@Before
fun setup() {
Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ Assume.assumeTrue(maxNum > 0)
testApp.enterDesktopMode(wmHelper, device)
- mailApp.launchViaIntent(wmHelper)
- newTasksApp.launchViaIntent(wmHelper)
- imeApp.launchViaIntent(wmHelper)
+ // Launch new [maxNum-1] tasks, which ends up opening [maxNum] tasks in total.
+ for (i in 1..maxNum - 1) {
+ mailApp.launchViaIntent(wmHelper)
+ }
}
@Test
open fun openAppToMinimizeWindow() {
- // Launch a new app while 4 apps are already open on desktop. This should result in the
- // first app we opened to be minimized.
- letterboxAppHelper.launchViaIntent(wmHelper)
+ // Launch a new tasks, which ends up opening [maxNum]+1 tasks in total. This should
+ // result in the first app we opened to be minimized.
+ mailApp.launchViaIntent(wmHelper)
}
@After
fun teardown() {
testApp.exit(wmHelper)
mailApp.exit(wmHelper)
- newTasksApp.exit(wmHelper)
- imeApp.exit(wmHelper)
- letterboxAppHelper.exit(wmHelper)
}
}
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/Android.bp b/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/Android.bp
deleted file mode 100644
index 85e6a8d1d865..000000000000
--- a/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/Android.bp
+++ /dev/null
@@ -1,38 +0,0 @@
-//
-// Copyright (C) 2020 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-package {
- // 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_test {
- name: "WMShellFlickerTestsMediaProjection",
- defaults: ["WMShellFlickerTestsDefault"],
- manifest: "AndroidManifest.xml",
- test_config_template: "AndroidTestTemplate.xml",
- srcs: ["src/**/*.kt"],
- static_libs: [
- "WMShellFlickerTestsBase",
- "WMShellScenariosMediaProjection",
- "WMShellTestUtils",
- ],
- data: ["trace_config/*"],
-}
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/AndroidManifest.xml b/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/AndroidManifest.xml
deleted file mode 100644
index 74b0daf3a2aa..000000000000
--- a/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/AndroidManifest.xml
+++ /dev/null
@@ -1,85 +0,0 @@
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- package="com.android.wm.shell.flicker">
-
- <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
- <!-- Read and write traces from external storage -->
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
- <!-- Allow the test to write directly to /sdcard/ -->
- <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
- <!-- Write secure settings -->
- <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
- <!-- Capture screen contents -->
- <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
- <!-- Enable / Disable tracing !-->
- <uses-permission android:name="android.permission.DUMP" />
- <!-- Run layers trace -->
- <uses-permission android:name="android.permission.HARDWARE_TEST"/>
- <!-- Capture screen recording -->
- <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"/>
- <!-- Workaround grant runtime permission exception from b/152733071 -->
- <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
- <uses-permission android:name="android.permission.READ_LOGS"/>
- <!-- Force-stop test apps -->
- <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES"/>
- <!-- Control test app's media session -->
- <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL"/>
- <!-- ATM.removeRootTasksWithActivityTypes() -->
- <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" />
- <!-- Enable bubble notification-->
- <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
- <!-- Allow the test to connect to perfetto trace processor -->
- <uses-permission android:name="android.permission.INTERNET"/>
- <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
- <uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" />
-
- <!-- Allow the test to write directly to /sdcard/ and connect to trace processor -->
- <application android:requestLegacyExternalStorage="true"
- android:networkSecurityConfig="@xml/network_security_config"
- android:largeHeap="true">
- <uses-library android:name="android.test.runner"/>
-
- <service android:name=".NotificationListener"
- android:exported="true"
- android:label="WMShellTestsNotificationListenerService"
- android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
- <intent-filter>
- <action android:name="android.service.notification.NotificationListenerService" />
- </intent-filter>
- </service>
-
- <service android:name="com.android.wm.shell.flicker.utils.MediaProjectionService"
- android:foregroundServiceType="mediaProjection"
- android:label="WMShellTestsMediaProjectionService"
- android:enabled="true">
- </service>
-
- <!-- (b/197936012) Remove startup provider due to test timeout issue -->
- <provider
- android:name="androidx.startup.InitializationProvider"
- android:authorities="${applicationId}.androidx-startup"
- tools:node="remove" />
- </application>
-
- <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.wm.shell.flicker"
- android:label="WindowManager Shell Flicker Tests">
- </instrumentation>
-</manifest>
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/AndroidTestTemplate.xml
deleted file mode 100644
index 40dbbac32c7f..000000000000
--- a/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/AndroidTestTemplate.xml
+++ /dev/null
@@ -1,97 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<configuration description="Runs WindowManager Shell Flicker Tests {MODULE}">
- <option name="test-tag" value="FlickerTests"/>
- <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
- <option name="isolated-storage" value="false"/>
-
- <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
- <!-- disable DeprecatedTargetSdk warning -->
- <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
- <!-- keeps the screen on during tests -->
- <option name="screen-always-on" value="on"/>
- <!-- prevents the phone from restarting -->
- <option name="force-skip-system-props" value="true"/>
- <!-- set WM tracing verbose level to all -->
- <option name="run-command" value="cmd window tracing level all"/>
- <!-- set WM tracing to frame (avoid incomplete states) -->
- <option name="run-command" value="cmd window tracing frame"/>
- <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests -->
- <option name="run-command" value="pm disable com.google.android.internal.betterbug"/>
- <!-- ensure lock screen mode is swipe -->
- <option name="run-command" value="locksettings set-disabled false"/>
- <!-- restart launcher to activate TAPL -->
- <option name="run-command"
- value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher"/>
- <!-- Increase trace size: 20mb for WM and 80mb for SF -->
- <option name="run-command" value="cmd window tracing size 20480"/>
- <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920"/>
- </target_preparer>
- <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
- <option name="test-user-token" value="%TEST_USER%"/>
- <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
- <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
- <option name="run-command" value="settings put system show_touches 1"/>
- <option name="run-command" value="settings put system pointer_location 1"/>
- <option name="teardown-command"
- value="settings delete secure show_ime_with_hard_keyboard"/>
- <option name="teardown-command" value="settings delete system show_touches"/>
- <option name="teardown-command" value="settings delete system pointer_location"/>
- <option name="teardown-command"
- value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/>
- </target_preparer>
- <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
- <option name="cleanup-apks" value="true"/>
- <option name="test-file-name" value="{MODULE}.apk"/>
- <option name="test-file-name" value="FlickerTestApp.apk"/>
- </target_preparer>
-
- <!-- Needed for pushing the trace config file -->
- <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
- <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
- <option name="push-file"
- key="trace_config.textproto"
- value="/data/misc/perfetto-traces/trace_config.textproto"
- />
- <!--Install the content provider automatically when we push some file in sdcard folder.-->
- <!--Needed to avoid the installation during the test suite.-->
- <option name="push-file" key="trace_config.textproto" value="/sdcard/sample.textproto"/>
- </target_preparer>
- <test class="com.android.tradefed.testtype.AndroidJUnitTest">
- <option name="package" value="{PACKAGE}"/>
- <option name="shell-timeout" value="6600s"/>
- <option name="test-timeout" value="6000s"/>
- <option name="hidden-api-checks" value="false"/>
- <option name="device-listeners" value="android.device.collectors.PerfettoListener"/>
- <!-- PerfettoListener related arguments -->
- <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/>
- <option name="instrumentation-arg"
- key="perfetto_config_file"
- value="trace_config.textproto"
- />
- <option name="instrumentation-arg" key="per_run" value="true"/>
- <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/>
- </test>
- <!-- Needed for pulling the collected trace config on to the host -->
- <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
- <option name="pull-pattern-keys" value="perfetto_file_path"/>
- <option name="directory-keys"
- value="/data/user/0/com.android.wm.shell.flicker/files"/>
- <option name="collect-on-run-ended-only" value="true"/>
- <option name="clean-up" value="true"/>
- </metrics_collector>
-</configuration>
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/trace_config/trace_config.textproto
deleted file mode 100644
index 9f2e49755fec..000000000000
--- a/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/trace_config/trace_config.textproto
+++ /dev/null
@@ -1,71 +0,0 @@
-# Copyright (C) 2023 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# proto-message: TraceConfig
-
-# Enable periodic flushing of the trace buffer into the output file.
-write_into_file: true
-
-# Writes the userspace buffer into the file every 1s.
-file_write_period_ms: 2500
-
-# See b/126487238 - we need to guarantee ordering of events.
-flush_period_ms: 30000
-
-# The trace buffers needs to be big enough to hold |file_write_period_ms| of
-# trace data. The trace buffer sizing depends on the number of trace categories
-# enabled and the device activity.
-
-# RSS events
-buffers: {
- size_kb: 63488
- fill_policy: RING_BUFFER
-}
-
-data_sources {
- config {
- name: "linux.process_stats"
- target_buffer: 0
- # polled per-process memory counters and process/thread names.
- # If you don't want the polled counters, remove the "process_stats_config"
- # section, but keep the data source itself as it still provides on-demand
- # thread/process naming for ftrace data below.
- process_stats_config {
- scan_all_processes_on_start: true
- }
- }
-}
-
-data_sources: {
- config {
- name: "linux.ftrace"
- ftrace_config {
- ftrace_events: "ftrace/print"
- ftrace_events: "task/task_newtask"
- ftrace_events: "task/task_rename"
- atrace_categories: "ss"
- atrace_categories: "wm"
- atrace_categories: "am"
- atrace_categories: "aidl"
- atrace_categories: "input"
- atrace_categories: "binder_driver"
- atrace_categories: "sched_process_exit"
- atrace_apps: "com.android.server.wm.flicker.testapp"
- atrace_apps: "com.android.systemui"
- atrace_apps: "com.android.wm.shell.flicker.service"
- atrace_apps: "com.google.android.apps.nexuslauncher"
- }
- }
-}
-
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartAppMediaProjectionFromSplitScreenTest.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartAppMediaProjectionFromSplitScreenTest.kt
new file mode 100644
index 000000000000..2b9772d9cbdd
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartAppMediaProjectionFromSplitScreenTest.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.scenarios.StartAppMediaProjectionFromSplitScreen
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/* Functional test for [StartAppMediaProjectionFromSplitScreen]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+class StartAppMediaProjectionFromSplitScreenTest() : StartAppMediaProjectionFromSplitScreen() \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartAppMediaProjectionInSplitScreenTest.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartAppMediaProjectionInSplitScreenTest.kt
new file mode 100644
index 000000000000..e92297b48166
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartAppMediaProjectionInSplitScreenTest.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.scenarios.StartAppMediaProjectionInSplitScreen
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/* Functional test for [StartAppMediaProjectionInSplitScreen]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+class StartAppMediaProjectionInSplitScreenTest() : StartAppMediaProjectionInSplitScreen() \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartAppMediaProjectionTest.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartAppMediaProjectionTest.kt
new file mode 100644
index 000000000000..3f8107592667
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartAppMediaProjectionTest.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.scenarios.StartAppMediaProjection
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/* Functional test for [StartAppMediaProjection]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+class StartAppMediaProjectionTest() : StartAppMediaProjection() \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartAppMediaProjectionWithExtraIntentTest.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartAppMediaProjectionWithExtraIntentTest.kt
new file mode 100644
index 000000000000..1975cc7f86d2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartAppMediaProjectionWithExtraIntentTest.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.scenarios.StartAppMediaProjectionWithExtraIntent
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/* Functional test for [StartAppMediaProjectionWithExtraIntent]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+class StartAppMediaProjectionWithExtraIntentTest : StartAppMediaProjectionWithExtraIntent() \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartRecentAppMediaProjectionFromSplitScreenTest.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartRecentAppMediaProjectionFromSplitScreenTest.kt
new file mode 100644
index 000000000000..943033c1819c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartRecentAppMediaProjectionFromSplitScreenTest.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.scenarios.StartRecentAppMediaProjectionFromSplitScreen
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/* Functional test for [StartRecentAppMediaProjectionFromSplitScreen]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+class StartRecentAppMediaProjectionFromSplitScreenTest() : StartRecentAppMediaProjectionFromSplitScreen() \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartRecentAppMediaProjectionInSplitScreenTest.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartRecentAppMediaProjectionInSplitScreenTest.kt
new file mode 100644
index 000000000000..6facfd5b0063
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartRecentAppMediaProjectionInSplitScreenTest.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.scenarios.StartRecentAppMediaProjectionInSplitScreen
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/* Functional test for [StartRecentAppMediaProjectionInSplitScreen]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+class StartRecentAppMediaProjectionInSplitScreenTest() : StartRecentAppMediaProjectionInSplitScreen() \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartRecentAppMediaProjectionTest.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartRecentAppMediaProjectionTest.kt
new file mode 100644
index 000000000000..bab09052715a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/functional/StartRecentAppMediaProjectionTest.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.scenarios.StartRecentAppMediaProjection
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/* Functional test for [StartRecentAppMediaProjection]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+class StartRecentAppMediaProjectionTest() : StartRecentAppMediaProjection() \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjection.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjection.kt
new file mode 100644
index 000000000000..fe2c57801f72
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjection.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.scenarios
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.device.apphelpers.CalculatorAppHelper
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.StartMediaProjectionAppHelper
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/**
+ * Test scenario which requests an a single-app MediaProjection session.
+ *
+ * This is for testing that the requested app is opened as expected upon selecting it from the app
+ * selector, so capture can proceed as expected.
+ */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class StartAppMediaProjection {
+
+ val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ val tapl = LauncherInstrumentation()
+ val wmHelper = WindowManagerStateHelper(instrumentation)
+ val device = UiDevice.getInstance(instrumentation)
+
+ private val initialRotation = Rotation.ROTATION_0
+ private val targetApp = CalculatorAppHelper(instrumentation)
+ private val testApp = StartMediaProjectionAppHelper(instrumentation)
+
+ @Rule
+ @JvmField
+ val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, initialRotation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(initialRotation.value)
+ testApp.launchViaIntent(wmHelper)
+ }
+
+ @Test
+ open fun startMediaProjection() {
+ testApp.startSingleAppMediaProjection(wmHelper, targetApp)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionFromSplitScreen.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionFromSplitScreen.kt
new file mode 100644
index 000000000000..3beece8c38b8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionFromSplitScreen.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.scenarios
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.device.apphelpers.CalculatorAppHelper
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.helpers.StartMediaProjectionAppHelper
+import com.android.wm.shell.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/**
+ * Test scenario which requests an a single-app MediaProjection session, while the HOST app is in
+ * split screen
+ *
+ * This is for testing that the requested app is opened as expected upon selecting it from the app
+ * selector, so capture can proceed as expected.
+ */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class StartAppMediaProjectionFromSplitScreen {
+
+ val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ val tapl = LauncherInstrumentation()
+ val wmHelper = WindowManagerStateHelper(instrumentation)
+ val device = UiDevice.getInstance(instrumentation)
+
+ private val initialRotation = Rotation.ROTATION_0
+ private val targetApp = CalculatorAppHelper(instrumentation)
+ private val simpleApp = SimpleAppHelper(instrumentation)
+ private val testApp = StartMediaProjectionAppHelper(instrumentation)
+
+ @Rule
+ @JvmField
+ val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, initialRotation)
+
+ @Before
+ fun setup() {
+ tapl.workspace.switchToOverview().dismissAllTasks()
+
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(initialRotation.value)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, simpleApp, testApp, initialRotation)
+ SplitScreenUtils.waitForSplitComplete(wmHelper, simpleApp, testApp)
+ }
+
+ @Test
+ open fun startMediaProjection() {
+ testApp.startSingleAppMediaProjection(wmHelper, targetApp)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionInSplitScreen.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionInSplitScreen.kt
new file mode 100644
index 000000000000..d3186ae88081
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionInSplitScreen.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.scenarios
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.device.apphelpers.CalculatorAppHelper
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.StartMediaProjectionAppHelper
+import com.android.wm.shell.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/**
+ * Test scenario which requests an a single-app MediaProjection session, while the TARGET app is in
+ * split screen (next to the host app)
+ *
+ * This is for testing that the split pair isn't broken, and capture still proceeds as expected
+ */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class StartAppMediaProjectionInSplitScreen {
+
+ val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ val tapl = LauncherInstrumentation()
+ val wmHelper = WindowManagerStateHelper(instrumentation)
+ val device = UiDevice.getInstance(instrumentation)
+
+ private val initialRotation = Rotation.ROTATION_0
+ private val targetApp = CalculatorAppHelper(instrumentation)
+ private val testApp = StartMediaProjectionAppHelper(instrumentation)
+
+ @Rule
+ @JvmField
+ val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, initialRotation)
+
+ @Before
+ fun setup() {
+ tapl.workspace.switchToOverview().dismissAllTasks()
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(initialRotation.value)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, targetApp, testApp, initialRotation)
+ }
+
+ @Test
+ open fun startMediaProjection() {
+ testApp.startSingleAppMediaProjection(wmHelper, targetApp)
+
+ wmHelper
+ .StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withWindowSurfaceAppeared(targetApp)
+ .withWindowSurfaceAppeared(testApp)
+ .waitForAndVerify()
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ targetApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithExtraIntent.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithExtraIntent.kt
new file mode 100644
index 000000000000..0b2a1ca23cdb
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithExtraIntent.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.scenarios
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.device.apphelpers.CalculatorAppHelper
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.StartMediaProjectionAppHelper
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/**
+ * Test scenario which requests an a single-app MediaProjection session but launches an intent to
+ * return to the home screen, before the intent for opening the requested app to capture.
+ *
+ * This is for testing that even if a different intent interrupts the process the launching the
+ * requested capture target, the MediaProjection process isn't interrupted and the device is still
+ * interactive.
+ */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class StartAppMediaProjectionWithExtraIntent {
+
+ val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ val tapl = LauncherInstrumentation()
+ val wmHelper = WindowManagerStateHelper(instrumentation)
+ val device = UiDevice.getInstance(instrumentation)
+
+ private val initialRotation = Rotation.ROTATION_0
+ private val targetApp = CalculatorAppHelper(instrumentation)
+ private val testApp = StartMediaProjectionAppHelper(instrumentation)
+
+ @Rule
+ @JvmField
+ val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, initialRotation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ testApp.launchViaIntent(wmHelper)
+ }
+
+ @Test
+ open fun startMediaProjection() {
+ testApp.startSingleAppMediaProjectionWithExtraIntent(wmHelper, targetApp)
+
+ // Check we can still interact with device after
+ tapl.workspace.switchToAllApps().getAppIcon(targetApp.appName).launch(targetApp.packageName)
+
+ wmHelper
+ .StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withWindowSurfaceAppeared(targetApp)
+ .waitForAndVerify()
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartRecentAppMediaProjection.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartRecentAppMediaProjection.kt
new file mode 100644
index 000000000000..30e0e4aa490a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartRecentAppMediaProjection.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.scenarios
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.device.apphelpers.CalculatorAppHelper
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.StartMediaProjectionAppHelper
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/**
+ * Test scenario which requests an a single-app MediaProjection session from recents.
+ *
+ * This is for testing that the app is started from recents and capture proceeds as expected.
+ */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class StartRecentAppMediaProjection {
+
+ val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ val tapl = LauncherInstrumentation()
+ val wmHelper = WindowManagerStateHelper(instrumentation)
+ val device = UiDevice.getInstance(instrumentation)
+
+ private val initialRotation = Rotation.ROTATION_0
+ private val targetApp = CalculatorAppHelper(instrumentation)
+ private val testApp = StartMediaProjectionAppHelper(instrumentation)
+
+ @Rule
+ @JvmField
+ val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, initialRotation)
+
+ @Before
+ fun setup() {
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(initialRotation.value)
+ targetApp.open()
+ testApp.launchViaIntent(wmHelper)
+ }
+
+ @Test
+ open fun startMediaProjection() {
+ testApp.startSingleAppMediaProjectionFromRecents(wmHelper, targetApp)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartRecentAppMediaProjectionFromSplitScreen.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartRecentAppMediaProjectionFromSplitScreen.kt
new file mode 100644
index 000000000000..f1dcf1fab140
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartRecentAppMediaProjectionFromSplitScreen.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.scenarios
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.device.apphelpers.CalculatorAppHelper
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.helpers.StartMediaProjectionAppHelper
+import com.android.wm.shell.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/**
+ * Test scenario which requests an a single-app MediaProjection session from recents while the HOST
+ * app is in split screen.
+ *
+ * This is for testing that the split pair isn't broken, and capture still proceeds as expected
+ */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class StartRecentAppMediaProjectionFromSplitScreen {
+
+ val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ val tapl = LauncherInstrumentation()
+ val wmHelper = WindowManagerStateHelper(instrumentation)
+ val device = UiDevice.getInstance(instrumentation)
+
+ private val initialRotation = Rotation.ROTATION_0
+ private val simpleApp = SimpleAppHelper(instrumentation)
+ private val targetApp = CalculatorAppHelper(instrumentation)
+ private val testApp = StartMediaProjectionAppHelper(instrumentation)
+
+ @Rule
+ @JvmField
+ val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, initialRotation)
+
+ @Before
+ fun setup() {
+ tapl.workspace.switchToOverview().dismissAllTasks()
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(initialRotation.value)
+ targetApp.open()
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, simpleApp, testApp, initialRotation)
+ }
+
+ @Test
+ open fun startMediaProjection() {
+ // The app we want to open for PSS will be the second item in the carousel,
+ // because the first will be the app open in split screen alongside the MediaProjection app
+ testApp.startSingleAppMediaProjectionFromRecents(wmHelper, targetApp, recentTasksIndex = 1)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartRecentAppMediaProjectionInSplitScreen.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartRecentAppMediaProjectionInSplitScreen.kt
new file mode 100644
index 000000000000..0a6992f9a152
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartRecentAppMediaProjectionInSplitScreen.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.scenarios
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.device.apphelpers.CalculatorAppHelper
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.StartMediaProjectionAppHelper
+import com.android.wm.shell.Utils
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/**
+ * Test scenario which requests an a single-app MediaProjection session from recents while the
+ * TARGET app is in split screen (with host app).
+ *
+ * This is for testing that the split pair isn't broken, and capture still proceeds as expected
+ */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class StartRecentAppMediaProjectionInSplitScreen {
+
+ val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ val tapl = LauncherInstrumentation()
+ val wmHelper = WindowManagerStateHelper(instrumentation)
+ val device = UiDevice.getInstance(instrumentation)
+
+ private val initialRotation = Rotation.ROTATION_0
+ private val targetApp = CalculatorAppHelper(instrumentation)
+ private val testApp = StartMediaProjectionAppHelper(instrumentation)
+
+ @Rule
+ @JvmField
+ val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, initialRotation)
+
+ @Before
+ fun setup() {
+ tapl.workspace.switchToOverview().dismissAllTasks()
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(initialRotation.value)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, targetApp, testApp, initialRotation)
+ }
+
+ @Test
+ open fun startMediaProjection() {
+ testApp.startSingleAppMediaProjectionFromRecents(wmHelper, targetApp)
+
+ wmHelper
+ .StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withWindowSurfaceAppeared(targetApp)
+ .withWindowSurfaceAppeared(testApp)
+ .waitForAndVerify()
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ targetApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MediaProjectionUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MediaProjectionUtils.kt
index f9706969ff11..8c2bdad364fc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MediaProjectionUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MediaProjectionUtils.kt
@@ -17,7 +17,11 @@
package com.android.wm.shell.flicker.utils
object MediaProjectionUtils {
- const val REQUEST_CODE: Int = 99
+ // Request code for the normal media projection request
+ const val REQUEST_CODE_NORMAL: Int = 11
+ // Request code for the media projection request which will include an extra intent to open
+ // home screen before starting requested app
+ const val REQUEST_CODE_EXTRA_INTENT: Int = 12
const val MSG_START_FOREGROUND_DONE: Int = 1
const val MSG_SERVICE_DESTROYED: Int = 2
const val EXTRA_MESSENGER: String = "messenger"
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index 6fa37885b724..ce640b5e5195 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -47,6 +47,7 @@ import android.view.WindowManager;
import androidx.test.filters.SmallTest;
+import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.bubbles.BubbleData.TimeSource;
import com.android.wm.shell.common.ShellExecutor;
@@ -102,6 +103,7 @@ public class BubbleDataTest extends ShellTestCase {
private BubbleData mBubbleData;
private TestableBubblePositioner mPositioner;
+ private UiEventLoggerFake mUiEventLogger;
@Mock
private TimeSource mTimeSource;
@@ -112,8 +114,6 @@ public class BubbleDataTest extends ShellTestCase {
@Mock
private PendingIntent mDeleteIntent;
@Mock
- private BubbleLogger mBubbleLogger;
- @Mock
private BubbleEducationController mEducationController;
@Mock
private ShellExecutor mMainExecutor;
@@ -196,10 +196,12 @@ public class BubbleDataTest extends ShellTestCase {
mock(Icon.class),
mMainExecutor, mBgExecutor);
+ mUiEventLogger = new UiEventLoggerFake();
+
mPositioner = new TestableBubblePositioner(mContext,
mContext.getSystemService(WindowManager.class));
- mBubbleData = new BubbleData(getContext(), mBubbleLogger, mPositioner, mEducationController,
- mMainExecutor, mBgExecutor);
+ mBubbleData = new BubbleData(getContext(), new BubbleLogger(mUiEventLogger), mPositioner,
+ mEducationController, mMainExecutor, mBgExecutor);
// Used by BubbleData to set lastAccessedTime
when(mTimeSource.currentTimeMillis()).thenReturn(1000L);
@@ -297,6 +299,82 @@ public class BubbleDataTest extends ShellTestCase {
}
@Test
+ public void testRemoveBubbleFromBubbleBar_notifCancelled_logEvent() {
+ mPositioner.setShowingInBubbleBar(true);
+
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ mBubbleData.setListener(mListener);
+
+ mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_NOTIF_CANCEL);
+ assertThat(mUiEventLogger.numLogs()).isEqualTo(1);
+ assertThat(mUiEventLogger.eventId(0)).isEqualTo(
+ BubbleLogger.Event.BUBBLE_BAR_BUBBLE_REMOVED_CANCELED.getId());
+ }
+
+ @Test
+ public void testRemoveBubbleFromBubbleBar_taskFinished_logEvent() {
+ mPositioner.setShowingInBubbleBar(true);
+
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ mBubbleData.setListener(mListener);
+
+ mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_TASK_FINISHED);
+ assertThat(mUiEventLogger.numLogs()).isEqualTo(1);
+ assertThat(mUiEventLogger.eventId(0)).isEqualTo(
+ BubbleLogger.Event.BUBBLE_BAR_BUBBLE_ACTIVITY_FINISH.getId());
+ }
+
+ @Test
+ public void testRemoveBubbleFromBubbleBar_notifBlocked_logEvent() {
+ mPositioner.setShowingInBubbleBar(true);
+
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ mBubbleData.setListener(mListener);
+
+ mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_BLOCKED);
+ assertThat(mUiEventLogger.numLogs()).isEqualTo(1);
+ assertThat(mUiEventLogger.eventId(0)).isEqualTo(
+ BubbleLogger.Event.BUBBLE_BAR_BUBBLE_REMOVED_BLOCKED.getId());
+ }
+
+ @Test
+ public void testRemoveBubbleFromBubbleBar_noLongerBubble_logEvent() {
+ mPositioner.setShowingInBubbleBar(true);
+
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ mBubbleData.setListener(mListener);
+
+ mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_NO_LONGER_BUBBLE);
+ assertThat(mUiEventLogger.numLogs()).isEqualTo(1);
+ assertThat(mUiEventLogger.eventId(0)).isEqualTo(
+ BubbleLogger.Event.BUBBLE_BAR_BUBBLE_REMOVED_BLOCKED.getId());
+ }
+
+ @Test
+ public void testRemoveBubbleFromBubbleBar_addToOverflow_logEvent() {
+ mPositioner.setShowingInBubbleBar(true);
+
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ mBubbleData.setListener(mListener);
+
+ mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_AGED);
+ assertThat(mUiEventLogger.numLogs()).isEqualTo(1);
+ assertThat(mUiEventLogger.eventId(0)).isEqualTo(
+ BubbleLogger.Event.BUBBLE_BAR_OVERFLOW_ADD_AGED.getId());
+ }
+
+ @Test
+ public void testRemoveBubble_notifCancelled_noLog() {
+ mPositioner.setShowingInBubbleBar(false);
+
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ mBubbleData.setListener(mListener);
+
+ mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_BLOCKED);
+ assertThat(mUiEventLogger.numLogs()).isEqualTo(0);
+ }
+
+ @Test
public void ifSuppress_hideFlyout() {
// Setup
mBubbleData.setListener(mListener);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 936835c34c04..5df395754c7a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -116,6 +116,7 @@ import com.android.wm.shell.recents.RecentsTransitionStateListener
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.UNKNOWN
import com.android.wm.shell.shared.split.SplitScreenConstants
+import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_UNDEFINED
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
@@ -3158,7 +3159,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
runOpenNewWindow(task)
verify(splitScreenController)
.startIntent(any(), anyInt(), any(), any(),
- optionsCaptor.capture(), anyOrNull(), eq(true)
+ optionsCaptor.capture(), anyOrNull(), eq(true), eq(SPLIT_INDEX_UNDEFINED)
)
assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode)
.isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
@@ -3174,7 +3175,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
verify(splitScreenController)
.startIntent(
any(), anyInt(), any(), any(),
- optionsCaptor.capture(), anyOrNull(), eq(true)
+ optionsCaptor.capture(), anyOrNull(), eq(true), eq(SPLIT_INDEX_UNDEFINED)
)
assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode)
.isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/SplitDragPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/SplitDragPolicyTest.java
index 2cfce6933e1b..0cf15baf30b0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/SplitDragPolicyTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/SplitDragPolicyTest.java
@@ -24,6 +24,7 @@ import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_UNDEFINED;
import static com.android.wm.shell.draganddrop.DragTestUtils.createAppClipData;
import static com.android.wm.shell.draganddrop.DragTestUtils.createIntentClipData;
import static com.android.wm.shell.draganddrop.DragTestUtils.createTaskInfo;
@@ -226,7 +227,7 @@ public class SplitDragPolicyTest extends ShellTestCase {
mPolicy.onDropped(filterTargetByType(targets, TYPE_FULLSCREEN), null /* hideTaskToken */);
verify(mFullscreenStarter).startIntent(any(), anyInt(), any(),
- eq(SPLIT_POSITION_UNDEFINED), any(), any());
+ eq(SPLIT_POSITION_UNDEFINED), any(), any(), eq(SPLIT_INDEX_UNDEFINED));
}
private void dragOverFullscreenApp_expectSplitScreenTargets(ClipData data) {
@@ -241,12 +242,12 @@ public class SplitDragPolicyTest extends ShellTestCase {
mPolicy.onDropped(filterTargetByType(targets, TYPE_SPLIT_LEFT), null /* hideTaskToken */);
verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
- eq(SPLIT_POSITION_TOP_OR_LEFT), any(), any());
+ eq(SPLIT_POSITION_TOP_OR_LEFT), any(), any(), eq(SPLIT_INDEX_UNDEFINED));
reset(mSplitScreenStarter);
mPolicy.onDropped(filterTargetByType(targets, TYPE_SPLIT_RIGHT), null /* hideTaskToken */);
verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
- eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any(), any());
+ eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any(), any(), eq(SPLIT_INDEX_UNDEFINED));
}
private void dragOverFullscreenAppPhone_expectVerticalSplitScreenTargets(ClipData data) {
@@ -261,13 +262,13 @@ public class SplitDragPolicyTest extends ShellTestCase {
mPolicy.onDropped(filterTargetByType(targets, TYPE_SPLIT_TOP), null /* hideTaskToken */);
verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
- eq(SPLIT_POSITION_TOP_OR_LEFT), any(), any());
+ eq(SPLIT_POSITION_TOP_OR_LEFT), any(), any(), eq(SPLIT_INDEX_UNDEFINED));
reset(mSplitScreenStarter);
mPolicy.onDropped(filterTargetByType(targets, TYPE_SPLIT_BOTTOM),
null /* hideTaskToken */);
verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
- eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any(), any());
+ eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any(), any(), eq(SPLIT_INDEX_UNDEFINED));
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 5f58265b45f5..289fd2d838fd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -36,6 +36,8 @@ import android.content.ComponentName;
import android.content.pm.ActivityInfo;
import android.graphics.Rect;
import android.os.RemoteException;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.Rational;
@@ -46,6 +48,7 @@ import android.window.WindowContainerToken;
import androidx.test.filters.SmallTest;
+import com.android.wm.shell.Flags;
import com.android.wm.shell.MockSurfaceControlHelper;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -67,6 +70,8 @@ import com.android.wm.shell.pip.phone.PhonePipMenuController;
import com.android.wm.shell.splitscreen.SplitScreenController;
import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatchers;
@@ -81,7 +86,13 @@ import java.util.Optional;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
+@DisableFlags(Flags.FLAG_ENABLE_PIP2)
public class PipTaskOrganizerTest extends ShellTestCase {
+ @ClassRule
+ public static final SetFlagsRule.ClassRule mClassRule = new SetFlagsRule.ClassRule();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = mClassRule.createSetFlagsRule();
+
private PipTaskOrganizer mPipTaskOrganizer;
@Mock private DisplayController mMockDisplayController;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 96003515a485..b123f4dfac9e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -42,11 +42,14 @@ import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
+import com.android.wm.shell.Flags;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.common.DisplayController;
@@ -74,6 +77,8 @@ import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -88,7 +93,11 @@ import java.util.Set;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
+@DisableFlags(Flags.FLAG_ENABLE_PIP2)
public class PipControllerTest extends ShellTestCase {
+ @ClassRule public static final SetFlagsRule.ClassRule mClassRule = new SetFlagsRule.ClassRule();
+ @Rule public final SetFlagsRule mSetFlagsRule = mClassRule.createSetFlagsRule();
+
private PipController mPipController;
private ShellInit mShellInit;
private ShellController mShellController;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 966651f19711..72a7a3f5ec99 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -23,6 +23,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_0;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
@@ -200,10 +201,11 @@ public class SplitScreenControllerTests extends ShellTestCase {
PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
- SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */);
+ SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */,
+ SPLIT_INDEX_0);
verify(mStageCoordinator).startIntent(eq(pendingIntent), mIntentCaptor.capture(),
- eq(SPLIT_POSITION_TOP_OR_LEFT), isNull(), isNull());
+ eq(SPLIT_POSITION_TOP_OR_LEFT), isNull(), isNull(), eq(SPLIT_INDEX_0));
assertEquals(FLAG_ACTIVITY_NO_USER_ACTION,
mIntentCaptor.getValue().getFlags() & FLAG_ACTIVITY_NO_USER_ACTION);
}
@@ -220,10 +222,11 @@ public class SplitScreenControllerTests extends ShellTestCase {
doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(any());
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
- SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */);
+ SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */,
+ SPLIT_INDEX_0);
verify(mStageCoordinator).startIntent(eq(pendingIntent), mIntentCaptor.capture(),
- eq(SPLIT_POSITION_TOP_OR_LEFT), isNull(), isNull());
+ eq(SPLIT_POSITION_TOP_OR_LEFT), isNull(), isNull(), eq(SPLIT_INDEX_0));
assertEquals(FLAG_ACTIVITY_MULTIPLE_TASK,
mIntentCaptor.getValue().getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK);
}
@@ -243,10 +246,11 @@ public class SplitScreenControllerTests extends ShellTestCase {
doReturn(sameTaskInfo).when(mRecentTasks).findTaskInBackground(any(), anyInt(), any());
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
- SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */);
+ SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */,
+ SPLIT_INDEX_0);
verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
- isNull(), isNull());
+ isNull(), isNull(), eq(SPLIT_INDEX_0));
verify(mMultiInstanceHelper, never()).supportsMultiInstanceSplit(any());
verify(mStageCoordinator, never()).switchSplitPosition(any());
}
@@ -269,10 +273,11 @@ public class SplitScreenControllerTests extends ShellTestCase {
.findTaskInBackground(any(), anyInt(), any());
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
- SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */);
+ SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */,
+ SPLIT_INDEX_0);
verify(mMultiInstanceHelper, never()).supportsMultiInstanceSplit(any());
verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
- isNull(), isNull());
+ isNull(), isNull(), eq(SPLIT_INDEX_0));
}
@Test
@@ -289,7 +294,8 @@ public class SplitScreenControllerTests extends ShellTestCase {
SPLIT_POSITION_BOTTOM_OR_RIGHT);
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
- SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */);
+ SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */,
+ SPLIT_INDEX_0);
verify(mStageCoordinator).switchSplitPosition(anyString());
}
@@ -301,7 +307,7 @@ public class SplitScreenControllerTests extends ShellTestCase {
PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */,
- true /* forceLaunchNewTask */);
+ true /* forceLaunchNewTask */, SPLIT_INDEX_0);
verify(mRecentTasks, never()).findTaskInBackground(any(), anyInt(), any());
}
@@ -312,7 +318,7 @@ public class SplitScreenControllerTests extends ShellTestCase {
PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */,
- false /* forceLaunchNewTask */);
+ false /* forceLaunchNewTask */, SPLIT_INDEX_0);
verify(mRecentTasks).findTaskInBackground(any(), anyInt(), any());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
index 66dcef6f14cc..d13a8888edc7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
@@ -53,8 +53,8 @@ public class SplitTestUtils {
doReturn(dividerBounds).when(out).getDividerBounds();
doReturn(dividerBounds).when(out).getRefDividerBounds();
doReturn(leash).when(out).getDividerLeash();
- doReturn(bounds1).when(out).getBounds1();
- doReturn(bounds2).when(out).getBounds2();
+ doReturn(bounds1).when(out).getTopLeftBounds();
+ doReturn(bounds2).when(out).getBottomRightBounds();
return out;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index ce3944a5855e..e32cf3899a03 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -27,6 +27,7 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
@@ -133,11 +134,11 @@ public class SplitTransitionTests extends ShellTestCase {
mSplitLayout = SplitTestUtils.createMockSplitLayout();
mMainStage = spy(new StageTaskListener(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
StageTaskListener.StageListenerCallbacks.class), mSyncQueue,
- mIconProvider, Optional.of(mWindowDecorViewModel)));
+ mIconProvider, Optional.of(mWindowDecorViewModel), STAGE_TYPE_MAIN));
mMainStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
mSideStage = spy(new StageTaskListener(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
StageTaskListener.StageListenerCallbacks.class), mSyncQueue,
- mIconProvider, Optional.of(mWindowDecorViewModel)));
+ mIconProvider, Optional.of(mWindowDecorViewModel), STAGE_TYPE_SIDE));
mSideStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY,
mSyncQueue, mTaskOrganizer, mMainStage, mSideStage, mDisplayController,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index a252a9db7095..4f6f3c69aa3a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.splitscreen;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_UNDEFINED;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
@@ -140,8 +141,8 @@ public class StageCoordinatorTests extends ShellTestCase {
Optional.empty()));
mDividerLeash = new SurfaceControl.Builder().setName("fakeDivider").build();
- when(mSplitLayout.getBounds1()).thenReturn(mBounds1);
- when(mSplitLayout.getBounds2()).thenReturn(mBounds2);
+ when(mSplitLayout.getTopLeftBounds()).thenReturn(mBounds1);
+ when(mSplitLayout.getBottomRightBounds()).thenReturn(mBounds2);
when(mSplitLayout.getRootBounds()).thenReturn(mRootBounds);
when(mSplitLayout.isLeftRightSplit()).thenReturn(false);
when(mSplitLayout.applyTaskChanges(any(), any(), any())).thenReturn(true);
@@ -165,8 +166,9 @@ public class StageCoordinatorTests extends ShellTestCase {
final WindowContainerTransaction wct = spy(new WindowContainerTransaction());
mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
+ // TODO(b/349828130) Address this once we remove index_undefined called
verify(mStageCoordinator).prepareEnterSplitScreen(eq(wct), eq(task),
- eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), eq(false));
+ eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), eq(false), eq(SPLIT_INDEX_UNDEFINED));
verify(mMainStage).reparentTopTask(eq(wct));
assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition());
assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition());
@@ -183,8 +185,9 @@ public class StageCoordinatorTests extends ShellTestCase {
final WindowContainerTransaction wct = new WindowContainerTransaction();
mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
+ // TODO(b/349828130) Address this once we remove index_undefined called
verify(mStageCoordinator).prepareEnterSplitScreen(eq(wct), eq(task),
- eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), eq(false));
+ eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), eq(false), eq(SPLIT_INDEX_UNDEFINED));
assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getMainStagePosition());
assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getSideStagePosition());
}
@@ -195,8 +198,9 @@ public class StageCoordinatorTests extends ShellTestCase {
final WindowContainerTransaction wct = new WindowContainerTransaction();
mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
+ // TODO(b/349828130) Address this once we remove index_undefined called
verify(mStageCoordinator).prepareEnterSplitScreen(eq(wct), eq(task),
- eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), eq(false));
+ eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), eq(false), eq(SPLIT_INDEX_UNDEFINED));
assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
index 7144a1e038f9..fe91440b106f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
@@ -19,6 +19,8 @@ package com.android.wm.shell.splitscreen;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertFalse;
@@ -93,7 +95,8 @@ public final class StageTaskListenerTests extends ShellTestCase {
mCallbacks,
mSyncQueue,
mIconProvider,
- Optional.of(mWindowDecorViewModel));
+ Optional.of(mWindowDecorViewModel),
+ STAGE_TYPE_UNDEFINED);
mRootTask = new TestRunningTaskInfoBuilder().build();
mRootTask.parentTaskId = INVALID_TASK_ID;
mSurfaceControl = new SurfaceControl.Builder().setName("test").build();
diff --git a/libs/hwui/DeviceInfo.h b/libs/hwui/DeviceInfo.h
index fb58a69747b3..b72e066e64ae 100644
--- a/libs/hwui/DeviceInfo.h
+++ b/libs/hwui/DeviceInfo.h
@@ -84,6 +84,7 @@ public:
// this value is only valid after the GPU has been initialized and there is a valid graphics
// context or if you are using the HWUI_NULL_GPU
int maxTextureSize() const;
+ bool hasMaxTextureSize() const { return mMaxTextureSize > 0; }
sk_sp<SkColorSpace> getWideColorSpace() const { return mWideColorSpace; }
SkColorType getWideColorType() {
static std::once_flag kFlag;
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 2c23864317a4..4801bd1038a3 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -186,7 +186,7 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) {
// If we are not a layer OR we cannot be rendered (eg, view was detached)
// we need to destroy any Layers we may have had previously
if (CC_LIKELY(layerType != LayerType::RenderLayer) || CC_UNLIKELY(!isRenderable()) ||
- CC_UNLIKELY(properties().getWidth() == 0) || CC_UNLIKELY(properties().getHeight() == 0) ||
+ CC_UNLIKELY(properties().getWidth() <= 0) || CC_UNLIKELY(properties().getHeight() <= 0) ||
CC_UNLIKELY(!properties().fitsOnLayer())) {
if (CC_UNLIKELY(hasLayer())) {
this->setLayerSurface(nullptr);
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index b1ad8b2eb1b9..4dc57004e401 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -545,7 +545,8 @@ public:
bool fitsOnLayer() const {
const DeviceInfo* deviceInfo = DeviceInfo::get();
return mPrimitiveFields.mWidth <= deviceInfo->maxTextureSize() &&
- mPrimitiveFields.mHeight <= deviceInfo->maxTextureSize();
+ mPrimitiveFields.mHeight <= deviceInfo->maxTextureSize() &&
+ mPrimitiveFields.mWidth > 0 && mPrimitiveFields.mHeight > 0;
}
bool promotedToLayer() const {
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 8ec04304a808..b36b8be10779 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -418,6 +418,11 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy
RenderNode* target) {
mRenderThread.removeFrameCallback(this);
+ // Make sure we have a valid device info
+ if (!DeviceInfo::get()->hasMaxTextureSize()) {
+ (void)mRenderThread.requireGrContext();
+ }
+
// If the previous frame was dropped we don't need to hold onto it, so
// just keep using the previous frame's structure instead
const auto reason = wasSkipped(mCurrentFrameInfo);
diff --git a/libs/hwui/tests/unit/RenderPropertiesTests.cpp b/libs/hwui/tests/unit/RenderPropertiesTests.cpp
index 3e8e0576bf49..6ec042cf23b0 100644
--- a/libs/hwui/tests/unit/RenderPropertiesTests.cpp
+++ b/libs/hwui/tests/unit/RenderPropertiesTests.cpp
@@ -40,7 +40,11 @@ TEST(RenderProperties, layerValidity) {
props.setLeftTopRightBottom(0, 0, maxTextureSize + 1, maxTextureSize + 1);
ASSERT_FALSE(props.fitsOnLayer());
- // Too small, but still 'fits'. Not fitting is an error case, so don't report empty as such.
+ // Too small, we can't create a layer for a 0 width or height
props.setLeftTopRightBottom(0, 0, 100, 0);
- ASSERT_TRUE(props.fitsOnLayer());
+ ASSERT_FALSE(props.fitsOnLayer());
+
+ // Can't create a negative-sized layer
+ props.setLeftTopRightBottom(0, 0, -100, 300);
+ ASSERT_FALSE(props.fitsOnLayer());
}
diff --git a/media/java/android/media/AudioDevicePort.java b/media/java/android/media/AudioDevicePort.java
index f5913c763b82..4b3962e6dd74 100644
--- a/media/java/android/media/AudioDevicePort.java
+++ b/media/java/android/media/AudioDevicePort.java
@@ -22,6 +22,7 @@ import android.os.Build;
import com.android.aconfig.annotations.VisibleForTesting;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -68,14 +69,14 @@ public class AudioDevicePort extends AudioPort {
return new AudioDevicePort(
new AudioHandle(/* id= */ 0),
/* name= */ "testAudioDevicePort",
- /* profiles= */ null,
+ /* profiles= */ new ArrayList<>(),
/* gains= */ null,
/* type= */ AudioManager.DEVICE_OUT_SPEAKER,
/* address= */ "testAddress",
/* speakerLayoutChannelMask= */ speakerLayoutChannelMask,
/* encapsulationModes= */ null,
/* encapsulationMetadataTypes= */ null,
- /* descriptors= */ null);
+ /* descriptors= */ new ArrayList<>());
}
private final int mType;
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 96edd63a9b12..782db358bf9f 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -1876,6 +1876,8 @@ public final class MediaCodecInfo {
* Codecs with this security model is not included in
* {@link MediaCodecList#REGULAR_CODECS}, but included in
* {@link MediaCodecList#ALL_CODECS}.
+ *
+ * @hide
*/
@FlaggedApi(FLAG_IN_PROCESS_SW_AUDIO_CODEC)
public static final int SECURITY_MODEL_TRUSTED_CONTENT_ONLY = 2;
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index bd65b2ecb76a..bc09aee9ac11 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -1748,6 +1748,7 @@ public final class MediaFormat {
(1 << MediaCodecInfo.SECURITY_MODEL_MEMORY_SAFE);
/**
* Flag for {@link MediaCodecInfo#SECURITY_MODEL_TRUSTED_CONTENT_ONLY}.
+ * @hide
*/
@FlaggedApi(FLAG_IN_PROCESS_SW_AUDIO_CODEC)
public static final int FLAG_SECURITY_MODEL_TRUSTED_CONTENT_ONLY =
@@ -1759,8 +1760,7 @@ public final class MediaFormat {
* The associated value is a flag of the following values:
* {@link FLAG_SECURITY_MODEL_SANDBOXED},
* {@link FLAG_SECURITY_MODEL_MEMORY_SAFE},
- * {@link FLAG_SECURITY_MODEL_TRUSTED_CONTENT_ONLY}. The default value is
- * {@link FLAG_SECURITY_MODEL_SANDBOXED}.
+ * The default value is {@link FLAG_SECURITY_MODEL_SANDBOXED}.
* <p>
* When passed to {@link MediaCodecList#findDecoderForFormat} or
* {@link MediaCodecList#findEncoderForFormat}, MediaCodecList filters
diff --git a/media/java/android/media/quality/IMediaQualityManager.aidl b/media/java/android/media/quality/IMediaQualityManager.aidl
index aaedf21999f7..1c85c7b97a6e 100644
--- a/media/java/android/media/quality/IMediaQualityManager.aidl
+++ b/media/java/android/media/quality/IMediaQualityManager.aidl
@@ -21,6 +21,7 @@ import android.media.quality.IAmbientBacklightCallback;
import android.media.quality.IPictureProfileCallback;
import android.media.quality.ISoundProfileCallback;
import android.media.quality.ParamCapability;
+import android.media.quality.PictureProfileHandle;
import android.media.quality.PictureProfile;
import android.media.quality.SoundProfile;
@@ -38,6 +39,7 @@ interface IMediaQualityManager {
List<String> getPictureProfilePackageNames();
List<String> getPictureProfileAllowList();
void setPictureProfileAllowList(in List<String> packages);
+ PictureProfileHandle getPictureProfileHandle(in String id);
SoundProfile createSoundProfile(in SoundProfile pp);
void updateSoundProfile(in String id, in SoundProfile pp);
diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java
index 28fe9b6c8112..43e884a8271e 100644
--- a/media/java/android/media/quality/MediaQualityManager.java
+++ b/media/java/android/media/quality/MediaQualityManager.java
@@ -271,6 +271,17 @@ public final class MediaQualityManager {
}
}
+ /**
+ * Gets picture profile handle by profile ID.
+ * @hide
+ */
+ public PictureProfileHandle getPictureProfileHandle(String id) {
+ try {
+ return mService.getPictureProfileHandle(id);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
/**
* Creates a picture profile and store it in the system.
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
index 898a8bf02edb..4de71235df8c 100644
--- a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
@@ -63,7 +63,7 @@ public class FrontendStatus {
FRONTEND_STATUS_TYPE_DVBT_CELL_IDS, FRONTEND_STATUS_TYPE_ATSC3_ALL_PLP_INFO,
FRONTEND_STATUS_TYPE_IPTV_CONTENT_URL, FRONTEND_STATUS_TYPE_IPTV_PACKETS_LOST,
FRONTEND_STATUS_TYPE_IPTV_PACKETS_RECEIVED, FRONTEND_STATUS_TYPE_IPTV_WORST_JITTER_MS,
- FRONTEND_STATUS_TYPE_IPTV_AVERAGE_JITTER_MS, FRONTEND_STATUS_TYPE_STANDARD_EXT})
+ FRONTEND_STATUS_TYPE_IPTV_AVERAGE_JITTER_MS, FRONTEND_STATUS_TYPE_STANDARD_EXTENSION})
@Retention(RetentionPolicy.SOURCE)
public @interface FrontendStatusType {}
@@ -317,7 +317,7 @@ public class FrontendStatus {
* Standard extension.
*/
@FlaggedApi(Flags.FLAG_TUNER_W_APIS)
- public static final int FRONTEND_STATUS_TYPE_STANDARD_EXT =
+ public static final int FRONTEND_STATUS_TYPE_STANDARD_EXTENSION =
android.hardware.tv.tuner.FrontendStatusType.STANDARD_EXT;
/** @hide */
@@ -567,7 +567,7 @@ public class FrontendStatus {
private Long mIptvPacketsReceived;
private Integer mIptvWorstJitterMs;
private Integer mIptvAverageJitterMs;
- private StandardExt mStandardExt;
+ private StandardExtension mStandardExtension;
// Constructed and fields set by JNI code.
private FrontendStatus() {
@@ -1298,12 +1298,12 @@ public class FrontendStatus {
*/
@NonNull
@FlaggedApi(Flags.FLAG_TUNER_W_APIS)
- public StandardExt getStandardExt() {
+ public StandardExtension getStandardExtension() {
TunerVersionChecker.checkHigherOrEqualVersionTo(
- TunerVersionChecker.TUNER_VERSION_4_0, "StandardExt status");
- if (mStandardExt == null) {
- throw new IllegalStateException("StandardExt status is empty");
+ TunerVersionChecker.TUNER_VERSION_4_0, "StandardExtension status");
+ if (mStandardExtension == null) {
+ throw new IllegalStateException("StandardExtension status is empty");
}
- return mStandardExt;
+ return mStandardExtension;
}
}
diff --git a/media/java/android/media/tv/tuner/frontend/StandardExt.java b/media/java/android/media/tv/tuner/frontend/StandardExtension.java
index 490727278b46..8bff3dd99cdb 100644
--- a/media/java/android/media/tv/tuner/frontend/StandardExt.java
+++ b/media/java/android/media/tv/tuner/frontend/StandardExtension.java
@@ -29,16 +29,16 @@ import android.media.tv.flags.Flags;
*/
@SystemApi
@FlaggedApi(Flags.FLAG_TUNER_W_APIS)
-public final class StandardExt {
- private final int mDvbsStandardExt;
- private final int mDvbtStandardExt;
+public final class StandardExtension {
+ private final int mDvbsStandardExtension;
+ private final int mDvbtStandardExtension;
/**
* Private constructor called by JNI only.
*/
- private StandardExt(int dvbsStandardExt, int dvbtStandardExt) {
- mDvbsStandardExt = dvbsStandardExt;
- mDvbtStandardExt = dvbtStandardExt;
+ private StandardExtension(int dvbsStandardExtension, int dvbtStandardExtension) {
+ mDvbsStandardExtension = dvbsStandardExtension;
+ mDvbtStandardExtension = dvbtStandardExtension;
}
/**
@@ -50,11 +50,11 @@ public final class StandardExt {
* @see android.media.tv.tuner.frontend.DvbsFrontendSettings
*/
@DvbsFrontendSettings.Standard
- public int getDvbsStandardExt() {
- if (mDvbsStandardExt == FrontendDvbsStandard.UNDEFINED) {
+ public int getDvbsStandardExtension() {
+ if (mDvbsStandardExtension == FrontendDvbsStandard.UNDEFINED) {
throw new IllegalStateException("No DVB-S standard transition");
}
- return mDvbsStandardExt;
+ return mDvbsStandardExtension;
}
/**
@@ -66,10 +66,10 @@ public final class StandardExt {
* @see android.media.tv.tuner.frontend.DvbtFrontendSettings
*/
@DvbtFrontendSettings.Standard
- public int getDvbtStandardExt() {
- if (mDvbtStandardExt == FrontendDvbtStandard.UNDEFINED) {
+ public int getDvbtStandardExtension() {
+ if (mDvbtStandardExtension == FrontendDvbtStandard.UNDEFINED) {
throw new IllegalStateException("No DVB-T standard transition");
}
- return mDvbtStandardExt;
+ return mDvbtStandardExtension;
}
}
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 80ca4f239a26..2fe069af638a 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -2940,10 +2940,10 @@ jobject JTuner::getFrontendStatus(jintArray types) {
break;
}
case FrontendStatus::Tag::standardExt: {
- jfieldID field = env->GetFieldID(clazz, "mStandardExt",
- "Landroid/media/tv/tuner/frontend/StandardExt;");
+ jfieldID field = env->GetFieldID(clazz, "mStandardExtension",
+ "Landroid/media/tv/tuner/frontend/StandardExtension;");
ScopedLocalRef standardExtClazz(env,
- env->FindClass("android/media/tv/tuner/frontend/StandardExt"));
+ env->FindClass("android/media/tv/tuner/frontend/StandardExtension"));
jmethodID initStandardExt = env->GetMethodID(standardExtClazz.get(), "<init>",
"(II)V");
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 2d1fbf9e7f66..6b41ddde5cc8 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -363,6 +363,7 @@ LIBANDROID {
APerformanceHint_reportActualWorkDuration2; # introduced=VanillaIceCream
APerformanceHint_notifyWorkloadIncrease; # introduced=36
APerformanceHint_notifyWorkloadReset; # introduced=36
+ APerformanceHint_borrowSessionFromJava; # introduced=36
AWorkDuration_create; # introduced=VanillaIceCream
AWorkDuration_release; # introduced=VanillaIceCream
AWorkDuration_setWorkPeriodStartTimestampNanos; # introduced=VanillaIceCream
@@ -383,6 +384,8 @@ LIBANDROID_PLATFORM {
APerformanceHint_setUseFMQForTesting;
APerformanceHint_getRateLimiterPropertiesForTesting;
APerformanceHint_setUseNewLoadHintBehaviorForTesting;
+ APerformanceHint_closeSessionFromJava;
+ APerformanceHint_createSessionFromJava;
extern "C++" {
ASurfaceControl_registerSurfaceStatsListener*;
ASurfaceControl_unregisterSurfaceStatsListener*;
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index e2fa94dd39bb..bc1945e37072 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -36,6 +36,7 @@
#include <cutils/trace.h>
#include <fmq/AidlMessageQueue.h>
#include <inttypes.h>
+#include <jni_wrappers.h>
#include <performance_hint_private.h>
#include <utils/SystemClock.h>
@@ -137,10 +138,14 @@ public:
APerformanceHintSession* createSession(const int32_t* threadIds, size_t size,
int64_t initialTargetWorkDurationNanos,
- hal::SessionTag tag = hal::SessionTag::APP);
+ hal::SessionTag tag = hal::SessionTag::APP,
+ bool isJava = false);
+ APerformanceHintSession* getSessionFromJava(JNIEnv* _Nonnull env, jobject _Nonnull sessionObj);
+
int64_t getPreferredRateNanos() const;
FMQWrapper& getFMQWrapper();
bool canSendLoadHints(std::vector<hal::SessionHint>& hints, int64_t now) REQUIRES(sHintMutex);
+ void initJava(JNIEnv* _Nonnull env);
private:
// Necessary to create an empty binder object
@@ -161,13 +166,16 @@ private:
FMQWrapper mFMQWrapper;
double mHintBudget = kMaxLoadHintsPerInterval;
int64_t mLastBudgetReplenish = 0;
+ bool mJavaInitialized = false;
+ jclass mJavaSessionClazz;
+ jfieldID mJavaSessionNativePtr;
};
struct APerformanceHintSession {
public:
APerformanceHintSession(std::shared_ptr<IHintManager> hintManager,
std::shared_ptr<IHintSession> session, int64_t preferredRateNanos,
- int64_t targetDurationNanos,
+ int64_t targetDurationNanos, bool isJava,
std::optional<hal::SessionConfig> sessionConfig);
APerformanceHintSession() = delete;
~APerformanceHintSession();
@@ -181,6 +189,7 @@ public:
int getThreadIds(int32_t* const threadIds, size_t* size);
int setPreferPowerEfficiency(bool enabled);
int reportActualWorkDuration(AWorkDuration* workDuration);
+ bool isJava();
private:
friend struct APerformanceHintManager;
@@ -203,6 +212,8 @@ private:
std::vector<int64_t> mLastHintSentTimestamp GUARDED_BY(sHintMutex);
// Cached samples
std::vector<hal::WorkDuration> mActualWorkDurations GUARDED_BY(sHintMutex);
+ // Is this session backing an SDK wrapper object
+ const bool mIsJava;
std::string mSessionName;
static int64_t sIDCounter GUARDED_BY(sHintMutex);
// The most recent set of thread IDs
@@ -299,7 +310,7 @@ bool APerformanceHintManager::canSendLoadHints(std::vector<hal::SessionHint>& hi
APerformanceHintSession* APerformanceHintManager::createSession(
const int32_t* threadIds, size_t size, int64_t initialTargetWorkDurationNanos,
- hal::SessionTag tag) {
+ hal::SessionTag tag, bool isJava) {
std::vector<int32_t> tids(threadIds, threadIds + size);
std::shared_ptr<IHintSession> session;
ndk::ScopedAStatus ret;
@@ -312,7 +323,7 @@ APerformanceHintSession* APerformanceHintManager::createSession(
return nullptr;
}
auto out = new APerformanceHintSession(mHintManager, std::move(session), mPreferredRateNanos,
- initialTargetWorkDurationNanos,
+ initialTargetWorkDurationNanos, isJava,
sessionConfig.id == -1
? std::nullopt
: std::make_optional<hal::SessionConfig>(
@@ -324,6 +335,18 @@ APerformanceHintSession* APerformanceHintManager::createSession(
return out;
}
+APerformanceHintSession* APerformanceHintManager::getSessionFromJava(JNIEnv* env,
+ jobject sessionObj) {
+ initJava(env);
+ LOG_ALWAYS_FATAL_IF(!env->IsInstanceOf(sessionObj, mJavaSessionClazz),
+ "Wrong java type passed to APerformanceHint_getSessionFromJava");
+ APerformanceHintSession* out = reinterpret_cast<APerformanceHintSession*>(
+ env->GetLongField(sessionObj, mJavaSessionNativePtr));
+ LOG_ALWAYS_FATAL_IF(out == nullptr, "Java-wrapped native hint session is nullptr");
+ LOG_ALWAYS_FATAL_IF(!out->isJava(), "Unmanaged native hint session returned from Java SDK");
+ return out;
+}
+
int64_t APerformanceHintManager::getPreferredRateNanos() const {
return mPreferredRateNanos;
}
@@ -332,13 +355,23 @@ FMQWrapper& APerformanceHintManager::getFMQWrapper() {
return mFMQWrapper;
}
+void APerformanceHintManager::initJava(JNIEnv* _Nonnull env) {
+ if (mJavaInitialized) {
+ return;
+ }
+ jclass sessionClazz = FindClassOrDie(env, "android/os/PerformanceHintManager$Session");
+ mJavaSessionClazz = MakeGlobalRefOrDie(env, sessionClazz);
+ mJavaSessionNativePtr = GetFieldIDOrDie(env, mJavaSessionClazz, "mNativeSessionPtr", "J");
+ mJavaInitialized = true;
+}
+
// ===================================== APerformanceHintSession implementation
constexpr int kNumEnums = enum_size<hal::SessionHint>();
APerformanceHintSession::APerformanceHintSession(std::shared_ptr<IHintManager> hintManager,
std::shared_ptr<IHintSession> session,
int64_t preferredRateNanos,
- int64_t targetDurationNanos,
+ int64_t targetDurationNanos, bool isJava,
std::optional<hal::SessionConfig> sessionConfig)
: mHintManager(hintManager),
mHintSession(std::move(session)),
@@ -347,6 +380,7 @@ APerformanceHintSession::APerformanceHintSession(std::shared_ptr<IHintManager> h
mFirstTargetMetTimestamp(0),
mLastTargetMetTimestamp(0),
mLastHintSentTimestamp(std::vector<int64_t>(kNumEnums, 0)),
+ mIsJava(isJava),
mSessionConfig(sessionConfig) {
if (sessionConfig->id > INT32_MAX) {
ALOGE("Session ID too large, must fit 32-bit integer");
@@ -401,6 +435,10 @@ int APerformanceHintSession::reportActualWorkDuration(int64_t actualDurationNano
return reportActualWorkDurationInternal(static_cast<AWorkDuration*>(&workDuration));
}
+bool APerformanceHintSession::isJava() {
+ return mIsJava;
+}
+
int APerformanceHintSession::sendHints(std::vector<hal::SessionHint>& hints, int64_t now,
const char*) {
std::scoped_lock lock(sHintMutex);
@@ -826,6 +864,22 @@ APerformanceHintSession* APerformanceHint_createSessionInternal(
static_cast<hal::SessionTag>(tag));
}
+APerformanceHintSession* APerformanceHint_createSessionFromJava(
+ APerformanceHintManager* manager, const int32_t* threadIds, size_t size,
+ int64_t initialTargetWorkDurationNanos) {
+ VALIDATE_PTR(manager)
+ VALIDATE_PTR(threadIds)
+ return manager->createSession(threadIds, size, initialTargetWorkDurationNanos,
+ hal::SessionTag::APP, true);
+}
+
+APerformanceHintSession* APerformanceHint_borrowSessionFromJava(JNIEnv* env,
+ jobject sessionObj) {
+ VALIDATE_PTR(env)
+ VALIDATE_PTR(sessionObj)
+ return APerformanceHintManager::getInstance()->getSessionFromJava(env, sessionObj);
+}
+
int64_t APerformanceHint_getPreferredUpdateRateNanos(APerformanceHintManager* manager) {
VALIDATE_PTR(manager)
return manager->getPreferredRateNanos();
@@ -846,6 +900,16 @@ int APerformanceHint_reportActualWorkDuration(APerformanceHintSession* session,
void APerformanceHint_closeSession(APerformanceHintSession* session) {
VALIDATE_PTR(session)
+ if (session->isJava()) {
+ LOG_ALWAYS_FATAL("%s: Java-owned PerformanceHintSession cannot be closed in native",
+ __FUNCTION__);
+ return;
+ }
+ delete session;
+}
+
+void APerformanceHint_closeSessionFromJava(APerformanceHintSession* session) {
+ VALIDATE_PTR(session)
delete session;
}
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 008120429c40..9a7a39f213ee 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -81,11 +81,14 @@ package android.nfc {
method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionSupported();
method public boolean isSecureNfcEnabled();
method public boolean isSecureNfcSupported();
+ method @FlaggedApi("android.nfc.nfc_check_tag_intent_preference") public boolean isTagIntentAllowed();
+ method @FlaggedApi("android.nfc.nfc_check_tag_intent_preference") public boolean isTagIntentAppPreferenceSupported();
method @FlaggedApi("android.nfc.enable_nfc_charging") public boolean isWlcEnabled();
method @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public void resetDiscoveryTechnology(@NonNull android.app.Activity);
method @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public void setDiscoveryTechnology(@NonNull android.app.Activity, int, int);
method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setObserveModeEnabled(boolean);
field public static final String ACTION_ADAPTER_STATE_CHANGED = "android.nfc.action.ADAPTER_STATE_CHANGED";
+ field @FlaggedApi("android.nfc.nfc_check_tag_intent_preference") public static final String ACTION_CHANGE_TAG_INTENT_PREFERENCE = "android.nfc.action.CHANGE_TAG_INTENT_PREFERENCE";
field public static final String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED";
field @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public static final String ACTION_PREFERRED_PAYMENT_CHANGED = "android.nfc.action.PREFERRED_PAYMENT_CHANGED";
field public static final String ACTION_TAG_DISCOVERED = "android.nfc.action.TAG_DISCOVERED";
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index f587660cae5b..15814edcd86a 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -11,7 +11,6 @@ package android.nfc {
method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public java.util.Map<java.lang.String,java.lang.Boolean> getTagIntentAppPreferenceForUser(int);
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOn();
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOnSupported();
- method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isTagIntentAppPreferenceSupported();
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void registerControllerAlwaysOnListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
method @FlaggedApi("android.nfc.nfc_vendor_cmd") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void registerNfcVendorNciCallback(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.NfcVendorNciCallback);
method @FlaggedApi("android.nfc.enable_nfc_charging") public void registerWlcStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.WlcStateListener);
@@ -97,6 +96,7 @@ package android.nfc {
method public void onDisableRequested(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public void onDisableStarted();
method public void onEeListenActivated(boolean);
+ method public void onEeUpdated();
method public void onEnableFinished(int);
method public void onEnableRequested(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public void onEnableStarted();
diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl
index 31514a09adad..a08b55fe86b8 100644
--- a/nfc/java/android/nfc/INfcAdapter.aidl
+++ b/nfc/java/android/nfc/INfcAdapter.aidl
@@ -121,4 +121,5 @@ interface INfcAdapter
List<Entry> getRoutingTableEntryList();
void indicateDataMigration(boolean inProgress, String pkg);
int commitRouting();
+ boolean isTagIntentAllowed(in String pkg, in int Userid);
}
diff --git a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
index 1a21c0bae413..e5eac0b4d6fd 100644
--- a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
+++ b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
@@ -48,6 +48,7 @@ interface INfcOemExtensionCallback {
void onRfFieldActivated(boolean isActivated);
void onRfDiscoveryStarted(boolean isDiscoveryStarted);
void onEeListenActivated(boolean isActivated);
+ void onEeUpdated();
void onGetOemAppSearchIntent(in List<String> firstPackage, in ResultReceiver intentConsumer);
void onNdefMessage(in Tag tag, in NdefMessage message, in ResultReceiver hasOemExecutableContent);
void onLaunchHceAppChooserActivity(in String selectedAid, in List<ApduServiceInfo> services, in ComponentName failedComponent, in String category);
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index c5d8191b22e6..056844f38f3c 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -49,6 +49,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.Log;
import java.io.IOException;
@@ -2505,22 +2506,22 @@ public final class NfcAdapter {
}
/**
- * Checks if the device supports Tag application preference.
+ * Checks if the device supports Tag Intent App Preference functionality.
+ *
+ * When supported, {@link #ACTION_NDEF_DISCOVERED}, {@link #ACTION_TECH_DISCOVERED} or
+ * {@link #ACTION_TAG_DISCOVERED} will not be dispatched to an Activity if
+ * {@link isTagIntentAllowed} returns {@code false}.
*
* @return {@code true} if the device supports Tag application preference, {@code false}
* otherwise
* @throws UnsupportedOperationException if FEATURE_NFC is unavailable
- *
- * @hide
*/
- @SystemApi
- @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @FlaggedApi(Flags.FLAG_NFC_CHECK_TAG_INTENT_PREFERENCE)
public boolean isTagIntentAppPreferenceSupported() {
if (!sHasNfcFeature) {
throw new UnsupportedOperationException();
}
return callServiceReturn(() -> sService.isTagIntentAppPreferenceSupported(), false);
-
}
/**
@@ -2895,4 +2896,42 @@ public final class NfcAdapter {
}
return mNfcOemExtension;
}
+
+ /**
+ * Activity action: Bring up the settings page that allows the user to enable or disable tag
+ * intent reception for apps.
+ *
+ * <p>This will direct user to the settings page shows a list that asks users whether
+ * they want to allow or disallow the package to start an activity when a tag is discovered.
+ *
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ @FlaggedApi(Flags.FLAG_NFC_CHECK_TAG_INTENT_PREFERENCE)
+ public static final String ACTION_CHANGE_TAG_INTENT_PREFERENCE =
+ "android.nfc.action.CHANGE_TAG_INTENT_PREFERENCE";
+
+ /**
+ * Checks whether the user has disabled the calling app from receiving NFC tag intents.
+ *
+ * <p>This method checks whether the caller package name is either not present in the user
+ * disabled list or is explicitly allowed by the user.
+ *
+ * @return {@code true} if an app is either not present in the list or is added to the list
+ * with the flag set to {@code true}. Otherwise, it returns {@code false}.
+ * It also returns {@code true} if {@link isTagIntentAppPreferenceSupported} returns
+ * {@code false}.
+ *
+ * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_CHECK_TAG_INTENT_PREFERENCE)
+ public boolean isTagIntentAllowed() {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ if (!isTagIntentAppPreferenceSupported()) {
+ return true;
+ }
+ return callServiceReturn(() -> sService.isTagIntentAllowed(mContext.getPackageName(),
+ UserHandle.myUserId()), false);
+ }
}
diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java
index 326ca6449c53..9ed678fe6014 100644
--- a/nfc/java/android/nfc/NfcOemExtension.java
+++ b/nfc/java/android/nfc/NfcOemExtension.java
@@ -367,6 +367,15 @@ public final class NfcOemExtension {
void onEeListenActivated(boolean isActivated);
/**
+ * Notifies that some NFCEE (NFC Execution Environment) has been updated.
+ *
+ * <p> This indicates that some applet has been installed/updated/removed in
+ * one of the NFCEE's.
+ * </p>
+ */
+ void onEeUpdated();
+
+ /**
* Gets the intent to find the OEM package in the OEM App market. If the consumer returns
* {@code null} or a timeout occurs, the intent from the first available package will be
* used instead.
@@ -830,6 +839,12 @@ public final class NfcOemExtension {
}
@Override
+ public void onEeUpdated() throws RemoteException {
+ mCallbackMap.forEach((cb, ex) ->
+ handleVoidCallback(null, (Object input) -> cb.onEeUpdated(), ex));
+ }
+
+ @Override
public void onStateUpdated(int state) throws RemoteException {
mCallbackMap.forEach((cb, ex) ->
handleVoidCallback(state, cb::onStateUpdated, ex));
diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig
index 8a37aa28cf9d..ee287aba709f 100644
--- a/nfc/java/android/nfc/flags.aconfig
+++ b/nfc/java/android/nfc/flags.aconfig
@@ -181,3 +181,11 @@ flag {
description: "Enable set service enabled for category other"
bug: "338157113"
}
+
+flag {
+ name: "nfc_check_tag_intent_preference"
+ is_exported: true
+ namespace: "nfc"
+ description: "App can check its tag intent preference status"
+ bug: "335916336"
+}
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
index fbf51fd7cca7..8c15b093e48e 100644
--- a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
+++ b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
@@ -218,7 +218,7 @@ public class PackageWatchdog {
@GuardedBy("sPackageWatchdogLock")
private static PackageWatchdog sPackageWatchdog;
- private final Object mLock = new Object();
+ private static final Object sLock = new Object();
// System server context
private final Context mContext;
// Handler to run short running tasks
@@ -228,7 +228,7 @@ public class PackageWatchdog {
// Contains (observer-name -> observer-handle) that have ever been registered from
// previous boots. Observers with all packages expired are periodically pruned.
// It is saved to disk on system shutdown and repouplated on startup so it survives reboots.
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
private final ArrayMap<String, ObserverInternal> mAllObservers = new ArrayMap<>();
// File containing the XML data of monitored packages /data/system/package-watchdog.xml
private final AtomicFile mPolicyFile;
@@ -244,26 +244,26 @@ public class PackageWatchdog {
private final Set<String> mPackagesExemptFromImpactLevelThreshold = new ArraySet<>();
// The set of packages that have been synced with the ExplicitHealthCheckController
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
private Set<String> mRequestedHealthCheckPackages = new ArraySet<>();
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
private boolean mIsPackagesReady;
// Flag to control whether explicit health checks are supported or not
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
private boolean mIsHealthCheckEnabled = DEFAULT_EXPLICIT_HEALTH_CHECK_ENABLED;
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
private int mTriggerFailureDurationMs = DEFAULT_TRIGGER_FAILURE_DURATION_MS;
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
private int mTriggerFailureCount = DEFAULT_TRIGGER_FAILURE_COUNT;
// SystemClock#uptimeMillis when we last executed #syncState
// 0 if no prune is scheduled.
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
private long mUptimeAtLastStateSync;
// If true, sync explicit health check packages with the ExplicitHealthCheckController.
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
private boolean mSyncRequired = false;
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
private long mLastMitigation = -1000000;
@FunctionalInterface
@@ -303,7 +303,11 @@ public class PackageWatchdog {
sPackageWatchdog = this;
}
- /** Creates or gets singleton instance of PackageWatchdog. */
+ /**
+ * Creates or gets singleton instance of PackageWatchdog.
+ *
+ * @param context The system server context.
+ */
public static @NonNull PackageWatchdog getInstance(@NonNull Context context) {
synchronized (sPackageWatchdogLock) {
if (sPackageWatchdog == null) {
@@ -319,7 +323,7 @@ public class PackageWatchdog {
* @hide
*/
public void onPackagesReady() {
- synchronized (mLock) {
+ synchronized (sLock) {
mIsPackagesReady = true;
mHealthCheckController.setCallbacks(packageName -> onHealthCheckPassed(packageName),
packages -> onSupportedPackages(packages),
@@ -338,7 +342,7 @@ public class PackageWatchdog {
* @hide
*/
public void registerHealthObserver(PackageHealthObserver observer) {
- synchronized (mLock) {
+ synchronized (sLock) {
ObserverInternal internalObserver = mAllObservers.get(observer.getUniqueIdentifier());
if (internalObserver != null) {
internalObserver.registeredObserver = observer;
@@ -405,7 +409,7 @@ public class PackageWatchdog {
mLongTaskHandler.post(() -> {
syncState("observing new packages");
- synchronized (mLock) {
+ synchronized (sLock) {
ObserverInternal oldObserver = mAllObservers.get(observer.getUniqueIdentifier());
if (oldObserver == null) {
Slog.d(TAG, observer.getUniqueIdentifier() + " started monitoring health "
@@ -437,7 +441,7 @@ public class PackageWatchdog {
*/
public void unregisterHealthObserver(PackageHealthObserver observer) {
mLongTaskHandler.post(() -> {
- synchronized (mLock) {
+ synchronized (sLock) {
mAllObservers.remove(observer.getUniqueIdentifier());
}
syncState("unregistering observer: " + observer.getUniqueIdentifier());
@@ -458,7 +462,7 @@ public class PackageWatchdog {
Slog.w(TAG, "Could not resolve a list of failing packages");
return;
}
- synchronized (mLock) {
+ synchronized (sLock) {
final long now = mSystemClock.uptimeMillis();
if (Flags.recoverabilityDetection()) {
if (now >= mLastMitigation
@@ -469,7 +473,7 @@ public class PackageWatchdog {
}
}
mLongTaskHandler.post(() -> {
- synchronized (mLock) {
+ synchronized (sLock) {
if (mAllObservers.isEmpty()) {
return;
}
@@ -569,7 +573,7 @@ public class PackageWatchdog {
int currentObserverImpact,
int mitigationCount) {
if (allowMitigations(currentObserverImpact, versionedPackage)) {
- synchronized (mLock) {
+ synchronized (sLock) {
mLastMitigation = mSystemClock.uptimeMillis();
}
currentObserverToNotify.onExecuteHealthCheckMitigation(versionedPackage, failureReason,
@@ -599,7 +603,7 @@ public class PackageWatchdog {
*/
@SuppressWarnings("GuardedBy")
public void noteBoot() {
- synchronized (mLock) {
+ synchronized (sLock) {
// if boot count has reached threshold, start mitigation.
// We wait until threshold number of restarts only for the first time. Perform
// mitigations for every restart after that.
@@ -652,7 +656,7 @@ public class PackageWatchdog {
// This currently adds about 7ms extra to shutdown thread
/** @hide Writes the package information to file during shutdown. */
public void writeNow() {
- synchronized (mLock) {
+ synchronized (sLock) {
// Must only run synchronous tasks as this runs on the ShutdownThread and no other
// thread is guaranteed to run during shutdown.
if (!mAllObservers.isEmpty()) {
@@ -671,7 +675,7 @@ public class PackageWatchdog {
* passed and the health check service is stopped.
*/
private void setExplicitHealthCheckEnabled(boolean enabled) {
- synchronized (mLock) {
+ synchronized (sLock) {
mIsHealthCheckEnabled = enabled;
mHealthCheckController.setEnabled(enabled);
mSyncRequired = true;
@@ -841,7 +845,10 @@ public class PackageWatchdog {
/**
* Returns {@code true} if this observer wishes to observe the given package, {@code false}
- * otherwise
+ * otherwise.
+ * Any failing package can be passed on to the observer. Currently the packages that have
+ * ANRs and perform {@link android.service.watchdog.ExplicitHealthCheckService} are being
+ * passed to observers in these API.
*
* <p> A persistent observer may choose to start observing certain failing packages, even if
* it has not explicitly asked to watch the package with {@link #startObservingHealth}.
@@ -853,14 +860,14 @@ public class PackageWatchdog {
@VisibleForTesting
long getTriggerFailureCount() {
- synchronized (mLock) {
+ synchronized (sLock) {
return mTriggerFailureCount;
}
}
@VisibleForTesting
long getTriggerFailureDurationMs() {
- synchronized (mLock) {
+ synchronized (sLock) {
return mTriggerFailureDurationMs;
}
}
@@ -881,7 +888,7 @@ public class PackageWatchdog {
*/
private void syncRequests() {
boolean syncRequired = false;
- synchronized (mLock) {
+ synchronized (sLock) {
if (mIsPackagesReady) {
Set<String> packages = getPackagesPendingHealthChecksLocked();
if (mSyncRequired || !packages.equals(mRequestedHealthCheckPackages)
@@ -918,7 +925,7 @@ public class PackageWatchdog {
Slog.i(TAG, "Health check passed for package: " + packageName);
boolean isStateChanged = false;
- synchronized (mLock) {
+ synchronized (sLock) {
for (int observerIdx = 0; observerIdx < mAllObservers.size(); observerIdx++) {
ObserverInternal observer = mAllObservers.valueAt(observerIdx);
MonitoredPackage monitoredPackage = observer.getMonitoredPackage(packageName);
@@ -946,7 +953,7 @@ public class PackageWatchdog {
supportedPackageTimeouts.put(info.getPackageName(), info.getHealthCheckTimeoutMillis());
}
- synchronized (mLock) {
+ synchronized (sLock) {
Slog.d(TAG, "Received supported packages " + supportedPackages);
Iterator<ObserverInternal> oit = mAllObservers.values().iterator();
while (oit.hasNext()) {
@@ -977,13 +984,13 @@ public class PackageWatchdog {
}
private void onSyncRequestNotified() {
- synchronized (mLock) {
+ synchronized (sLock) {
mSyncRequired = true;
syncRequestsAsync();
}
}
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
private Set<String> getPackagesPendingHealthChecksLocked() {
Set<String> packages = new ArraySet<>();
Iterator<ObserverInternal> oit = mAllObservers.values().iterator();
@@ -1009,7 +1016,7 @@ public class PackageWatchdog {
* health check service and schedules the next state sync.
*/
private void syncState(String reason) {
- synchronized (mLock) {
+ synchronized (sLock) {
Slog.i(TAG, "Syncing state, reason: " + reason);
pruneObserversLocked();
@@ -1025,7 +1032,7 @@ public class PackageWatchdog {
syncState("scheduled");
}
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
private void scheduleNextSyncStateLocked() {
long durationMs = getNextStateSyncMillisLocked();
mShortTaskHandler.removeCallbacks(mSyncStateWithScheduledReason);
@@ -1043,7 +1050,7 @@ public class PackageWatchdog {
*
* @returns Long#MAX_VALUE if there are no observed packages.
*/
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
private long getNextStateSyncMillisLocked() {
long shortestDurationMs = Long.MAX_VALUE;
for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
@@ -1064,7 +1071,7 @@ public class PackageWatchdog {
* Removes {@code elapsedMs} milliseconds from all durations on monitored packages
* and updates other internal state.
*/
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
private void pruneObserversLocked() {
long elapsedMs = mUptimeAtLastStateSync == 0
? 0 : mSystemClock.uptimeMillis() - mUptimeAtLastStateSync;
@@ -1092,7 +1099,7 @@ public class PackageWatchdog {
private void onHealthCheckFailed(ObserverInternal observer,
Set<MonitoredPackage> failedPackages) {
mLongTaskHandler.post(() -> {
- synchronized (mLock) {
+ synchronized (sLock) {
PackageHealthObserver registeredObserver = observer.registeredObserver;
if (registeredObserver != null) {
Iterator<MonitoredPackage> it = failedPackages.iterator();
@@ -1201,7 +1208,7 @@ public class PackageWatchdog {
*/
@VisibleForTesting
void updateConfigs() {
- synchronized (mLock) {
+ synchronized (sLock) {
mTriggerFailureCount = DeviceConfig.getInt(
DeviceConfig.NAMESPACE_ROLLBACK,
PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
@@ -1230,7 +1237,7 @@ public class PackageWatchdog {
*/
private boolean saveToFile() {
Slog.i(TAG, "Saving observer state to file");
- synchronized (mLock) {
+ synchronized (sLock) {
FileOutputStream stream;
try {
stream = mPolicyFile.startWrite();
@@ -1297,20 +1304,38 @@ public class PackageWatchdog {
/** Dump status of every observer in mAllObservers. */
public void dump(@NonNull PrintWriter pw) {
- if (Flags.synchronousRebootInRescueParty() && RescueParty.isRecoveryTriggeredReboot()) {
+ if (Flags.synchronousRebootInRescueParty() && isRecoveryTriggeredReboot()) {
dumpInternal(pw);
} else {
- synchronized (mLock) {
+ synchronized (sLock) {
dumpInternal(pw);
}
}
}
+ /**
+ * Check if we're currently attempting to reboot during mitigation. This method must return
+ * true if triggered reboot early during a boot loop, since the device will not be fully booted
+ * at this time.
+ * @hide
+ */
+ public static boolean isRecoveryTriggeredReboot() {
+ return isFactoryResetPropertySet() || isRebootPropertySet();
+ }
+
+ private static boolean isFactoryResetPropertySet() {
+ return CrashRecoveryProperties.attemptingFactoryReset().orElse(false);
+ }
+
+ private static boolean isRebootPropertySet() {
+ return CrashRecoveryProperties.attemptingReboot().orElse(false);
+ }
+
private void dumpInternal(@NonNull PrintWriter pw) {
IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
ipw.println("Package Watchdog status");
ipw.increaseIndent();
- synchronized (mLock) {
+ synchronized (sLock) {
for (String observerName : mAllObservers.keySet()) {
ipw.println("Observer name: " + observerName);
ipw.increaseIndent();
@@ -1324,7 +1349,7 @@ public class PackageWatchdog {
}
@VisibleForTesting
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
void registerObserverInternal(ObserverInternal observerInternal) {
mAllObservers.put(observerInternal.name, observerInternal);
}
@@ -1333,15 +1358,15 @@ public class PackageWatchdog {
* Represents an observer monitoring a set of packages along with the failure thresholds for
* each package.
*
- * <p> Note, the PackageWatchdog#mLock must always be held when reading or writing
+ * <p> Note, the PackageWatchdog#sLock must always be held when reading or writing
* instances of this class.
*/
static class ObserverInternal {
public final String name;
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
private final ArrayMap<String, MonitoredPackage> mPackages = new ArrayMap<>();
@Nullable
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
public PackageHealthObserver registeredObserver;
private int mMitigationCount;
@@ -1359,7 +1384,7 @@ public class PackageWatchdog {
* Writes important {@link MonitoredPackage} details for this observer to file.
* Does not persist any package failure thresholds.
*/
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
public boolean writeLocked(XmlSerializer out) {
try {
out.startTag(null, TAG_OBSERVER);
@@ -1387,7 +1412,7 @@ public class PackageWatchdog {
mMitigationCount = mitigationCount;
}
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
public void updatePackagesLocked(List<MonitoredPackage> packages) {
for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
MonitoredPackage p = packages.get(pIndex);
@@ -1410,7 +1435,7 @@ public class PackageWatchdog {
* health check passing, or an empty list if no package expired for which an explicit health
* check was still pending
*/
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
private Set<MonitoredPackage> prunePackagesLocked(long elapsedMs) {
Set<MonitoredPackage> failedPackages = new ArraySet<>();
Iterator<MonitoredPackage> it = mPackages.values().iterator();
@@ -1435,7 +1460,7 @@ public class PackageWatchdog {
* @returns {@code true} if failure threshold is exceeded, {@code false} otherwise
* @hide
*/
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
public boolean onPackageFailureLocked(String packageName) {
if (getMonitoredPackage(packageName) == null && registeredObserver.isPersistent()
&& registeredObserver.mayObservePackage(packageName)) {
@@ -1454,7 +1479,7 @@ public class PackageWatchdog {
*
* @return a mapping of package names to {@link MonitoredPackage} objects.
*/
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
public ArrayMap<String, MonitoredPackage> getMonitoredPackages() {
return mPackages;
}
@@ -1467,7 +1492,7 @@ public class PackageWatchdog {
* @return the {@link MonitoredPackage} object associated with the package name if one
* exists, {@code null} otherwise.
*/
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
@Nullable
public MonitoredPackage getMonitoredPackage(String packageName) {
return mPackages.get(packageName);
@@ -1478,7 +1503,7 @@ public class PackageWatchdog {
*
* @param p: the {@link MonitoredPackage} to store.
*/
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
public void putMonitoredPackage(MonitoredPackage p) {
mPackages.put(p.getName(), p);
}
@@ -1601,17 +1626,17 @@ public class PackageWatchdog {
* Represents a package and its health check state along with the time
* it should be monitored for.
*
- * <p> Note, the PackageWatchdog#mLock must always be held when reading or writing
+ * <p> Note, the PackageWatchdog#sLock must always be held when reading or writing
* instances of this class.
*/
class MonitoredPackage {
private final String mPackageName;
// Times when package failures happen sorted in ascending order
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
private final LongArrayQueue mFailureHistory = new LongArrayQueue();
// Times when an observer was called to mitigate this package's failure. Sorted in
// ascending order.
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
private final LongArrayQueue mMitigationCalls;
// One of STATE_[ACTIVE|INACTIVE|PASSED|FAILED]. Updated on construction and after
// methods that could change the health check state: handleElapsedTimeLocked and
@@ -1620,17 +1645,17 @@ public class PackageWatchdog {
// Whether an explicit health check has passed.
// This value in addition with mHealthCheckDurationMs determines the health check state
// of the package, see #getHealthCheckStateLocked
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
private boolean mHasPassedHealthCheck;
// System uptime duration to monitor package.
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
private long mDurationMs;
// System uptime duration to check the result of an explicit health check
// Initially, MAX_VALUE until we get a value from the health check service
// and request health checks.
// This value in addition with mHasPassedHealthCheck determines the health check state
// of the package, see #getHealthCheckStateLocked
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
private long mHealthCheckDurationMs = Long.MAX_VALUE;
MonitoredPackage(String packageName, long durationMs,
@@ -1647,7 +1672,7 @@ public class PackageWatchdog {
/** Writes the salient fields to disk using {@code out}.
* @hide
*/
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
public void writeLocked(XmlSerializer out) throws IOException {
out.startTag(null, TAG_PACKAGE);
out.attribute(null, ATTR_NAME, getName());
@@ -1665,7 +1690,7 @@ public class PackageWatchdog {
*
* @return {@code true} if failure count exceeds a threshold, {@code false} otherwise
*/
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
public boolean onFailureLocked() {
// Sliding window algorithm: find out if there exists a window containing failures >=
// mTriggerFailureCount.
@@ -1685,7 +1710,7 @@ public class PackageWatchdog {
/**
* Notes the timestamp of a mitigation call into the observer.
*/
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
public void noteMitigationCallLocked() {
mMitigationCalls.addLast(mSystemClock.uptimeMillis());
}
@@ -1696,7 +1721,7 @@ public class PackageWatchdog {
*
* @return the number of mitigation calls made in the de-escalation window.
*/
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
public int getMitigationCountLocked() {
try {
final long now = mSystemClock.uptimeMillis();
@@ -1716,7 +1741,7 @@ public class PackageWatchdog {
*
* @return a LongArrayQueue of the mitigation calls relative to the current system uptime.
*/
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
public LongArrayQueue normalizeMitigationCalls() {
LongArrayQueue normalized = new LongArrayQueue();
final long now = mSystemClock.uptimeMillis();
@@ -1731,7 +1756,7 @@ public class PackageWatchdog {
*
* @return the new health check state
*/
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
public int setHealthCheckActiveLocked(long initialHealthCheckDurationMs) {
if (initialHealthCheckDurationMs <= 0) {
Slog.wtf(TAG, "Cannot set non-positive health check duration "
@@ -1751,7 +1776,7 @@ public class PackageWatchdog {
*
* @return the new health check state
*/
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
public int handleElapsedTimeLocked(long elapsedMs) {
if (elapsedMs <= 0) {
Slog.w(TAG, "Cannot handle non-positive elapsed time for package " + getName());
@@ -1769,7 +1794,7 @@ public class PackageWatchdog {
}
/** Explicitly update the monitoring duration of the package. */
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
public void updateHealthCheckDuration(long newDurationMs) {
mDurationMs = newDurationMs;
}
@@ -1780,7 +1805,7 @@ public class PackageWatchdog {
*
* @return the new {@link HealthCheckState health check state}
*/
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
@HealthCheckState
public int tryPassHealthCheckLocked() {
if (mHealthCheckState != HealthCheckState.FAILED) {
@@ -1799,7 +1824,7 @@ public class PackageWatchdog {
/**
* Returns the current {@link HealthCheckState health check state}.
*/
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
@HealthCheckState
public int getHealthCheckStateLocked() {
return mHealthCheckState;
@@ -1810,7 +1835,7 @@ public class PackageWatchdog {
*
* @return the duration or {@link Long#MAX_VALUE} if the package should not be scheduled
*/
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
public long getShortestScheduleDurationMsLocked() {
// Consider health check duration only if #isPendingHealthChecksLocked is true
return Math.min(toPositive(mDurationMs),
@@ -1822,7 +1847,7 @@ public class PackageWatchdog {
* Returns {@code true} if the total duration left to monitor the package is less than or
* equal to 0 {@code false} otherwise.
*/
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
public boolean isExpiredLocked() {
return mDurationMs <= 0;
}
@@ -1831,7 +1856,7 @@ public class PackageWatchdog {
* Returns {@code true} if the package, {@link #getName} is expecting health check results
* {@code false} otherwise.
*/
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
public boolean isPendingHealthChecksLocked() {
return mHealthCheckState == HealthCheckState.ACTIVE
|| mHealthCheckState == HealthCheckState.INACTIVE;
@@ -1843,7 +1868,7 @@ public class PackageWatchdog {
*
* @return the new {@link HealthCheckState health check state}
*/
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
@HealthCheckState
private int updateHealthCheckStateLocked() {
int oldState = mHealthCheckState;
@@ -1898,7 +1923,7 @@ public class PackageWatchdog {
}
}
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
@SuppressWarnings("GuardedBy")
void saveAllObserversBootMitigationCountToMetadata(String filePath) {
HashMap<String, Integer> bootMitigationCounts = new HashMap<>();
@@ -2001,7 +2026,7 @@ public class PackageWatchdog {
/** Increments the boot counter, and returns whether the device is bootlooping. */
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
public boolean incrementAndTest() {
if (Flags.recoverabilityDetection()) {
readAllObserversBootMitigationCountIfNecessary(METADATA_FILE);
@@ -2042,7 +2067,7 @@ public class PackageWatchdog {
}
}
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
private boolean performedMitigationsDuringWindow() {
for (ObserverInternal observerInternal: mAllObservers.values()) {
if (observerInternal.getBootMitigationCount() > 0) {
@@ -2052,7 +2077,7 @@ public class PackageWatchdog {
return false;
}
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
private void resetAllObserversBootMitigationCount() {
for (int i = 0; i < mAllObservers.size(); i++) {
final ObserverInternal observer = mAllObservers.valueAt(i);
@@ -2061,7 +2086,7 @@ public class PackageWatchdog {
saveAllObserversBootMitigationCountToMetadata(METADATA_FILE);
}
- @GuardedBy("mLock")
+ @GuardedBy("sLock")
@SuppressWarnings("GuardedBy")
void readAllObserversBootMitigationCountIfNecessary(String filePath) {
File metadataFile = new File(filePath);
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/ModifierExt.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/ModifierExt.kt
index e883a4a55af9..25bb46c837f6 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/ModifierExt.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/ModifierExt.kt
@@ -25,3 +25,36 @@ fun Modifier.contentDescription(contentDescription: String?) =
if (contentDescription != null) this.semantics {
this.contentDescription = contentDescription
} else this
+
+/**
+ * Concatenates this modifier with another if `condition` is true.
+ *
+ * This method allows inline conditional addition of modifiers to a modifier chain. Instead of
+ * writing
+ *
+ * ```
+ * val aModifier = Modifier.a()
+ * val bModifier = if(condition) aModifier.b() else aModifier
+ * Composable(modifier = bModifier)
+ * ```
+ *
+ * You can instead write
+ *
+ * ```
+ * Composable(modifier = Modifier.a().thenIf(condition){
+ * Modifier.b()
+ * }
+ * ```
+ *
+ * This makes the modifier chain easier to read.
+ *
+ * Note that unlike the non-factory version, the conditional modifier is recreated each time, and
+ * may never be created at all.
+ *
+ * @param condition Whether or not to apply the modifiers.
+ * @param factory Creates the modifier to concatenate with the current one.
+ * @return a Modifier representing this modifier followed by other in sequence.
+ * @see Modifier.then
+ */
+inline fun Modifier.thenIf(condition: Boolean, crossinline factory: () -> Modifier): Modifier =
+ if (condition) this.then(factory()) else this
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
index acb96be64a34..b1bb79d61b03 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
@@ -36,12 +36,13 @@ import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.min
+import com.android.settingslib.spa.framework.compose.thenIf
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsOpacity.alphaForEnabled
import com.android.settingslib.spa.framework.theme.SettingsShape
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled
+import com.android.settingslib.spa.widget.ui.LocalIsInCategory
import com.android.settingslib.spa.widget.ui.SettingsTitle
@Composable
@@ -57,18 +58,18 @@ internal fun BaseLayout(
paddingVertical: Dp = SettingsDimension.itemPaddingVertical,
widget: @Composable () -> Unit = {},
) {
+ val surfaceBright = MaterialTheme.colorScheme.surfaceBright
Row(
modifier =
modifier
.fillMaxWidth()
.semantics(mergeDescendants = true) {}
- .then(
- if (isSpaExpressiveEnabled)
- Modifier.heightIn(min = SettingsDimension.preferenceMinHeight)
- .clip(SettingsShape.CornerExtraSmall)
- .background(MaterialTheme.colorScheme.surfaceBright)
- else Modifier
- )
+ .thenIf(isSpaExpressiveEnabled) {
+ Modifier.heightIn(min = SettingsDimension.preferenceMinHeight)
+ }
+ .thenIf(isSpaExpressiveEnabled && LocalIsInCategory.current) {
+ Modifier.clip(SettingsShape.CornerExtraSmall).background(surfaceBright)
+ }
.padding(end = paddingEnd),
verticalAlignment = Alignment.CenterVertically,
) {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt
index 28b2b4ab1662..96d2abb70391 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt
@@ -31,6 +31,8 @@ import androidx.compose.material.icons.outlined.TouchApp
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -99,7 +101,7 @@ fun Category(title: String? = null, content: @Composable ColumnScope.() -> Unit)
verticalArrangement =
if (isSpaExpressiveEnabled) Arrangement.spacedBy(SettingsDimension.paddingTiny)
else Arrangement.Top,
- content = content,
+ content = { CompositionLocalProvider(LocalIsInCategory provides true) { content() } },
)
}
}
@@ -109,15 +111,14 @@ fun Category(title: String? = null, content: @Composable ColumnScope.() -> Unit)
*
* @param list The list of items to display.
* @param entry The entry for each list item according to its index in list.
- * @param key Optional. The key for each item in list to provide unique item identifiers, making
- * the list more efficient.
- * @param title Optional. Category title for each item or each group of items in the list. It
- * should be decided by the index.
+ * @param key Optional. The key for each item in list to provide unique item identifiers, making the
+ * list more efficient.
+ * @param title Optional. Category title for each item or each group of items in the list. It should
+ * be decided by the index.
* @param bottomPadding Optional. Bottom outside padding of the category.
* @param state Optional. State of LazyList.
* @param content Optional. Content to be shown at the top of the category.
*/
-
@Composable
fun LazyCategory(
list: List<Any>,
@@ -144,17 +145,19 @@ fun LazyCategory(
verticalArrangement = Arrangement.spacedBy(SettingsDimension.paddingTiny),
state = state,
) {
- item { content() }
+ item { CompositionLocalProvider(LocalIsInCategory provides true) { content() } }
items(count = list.size, key = key) {
title?.invoke(it)?.let { title -> CategoryTitle(title) }
- val entryPreference = entry(it)
- entryPreference()
+ CompositionLocalProvider(LocalIsInCategory provides true) { entry(it)() }
}
}
}
}
+/** LocalIsInCategory containing the if the current composable is in a category. */
+internal val LocalIsInCategory = compositionLocalOf { false }
+
@Preview
@Composable
private fun CategoryPreview() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index d0827b30efc9..4eb0567c67d9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -133,8 +133,9 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
* If an ACTION_UUID intent comes in within
* MAX_UUID_DELAY_FOR_AUTO_CONNECT milliseconds, we will try auto-connect
* again with the new UUIDs
+ * The value is reset if a manual disconnection happens.
*/
- private long mConnectAttempted;
+ private long mConnectAttempted = -1;
// Active device state
private boolean mIsActiveDeviceA2dp = false;
@@ -369,6 +370,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
}
public void disconnect() {
+ mConnectAttempted = -1;
synchronized (mProfileLock) {
if (getGroupId() != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
for (CachedBluetoothDevice member : getMemberDevice()) {
@@ -983,15 +985,19 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
}
if (BluetoothUtils.D) {
- Log.d(TAG, "onUuidChanged: Time since last connect="
- + (SystemClock.elapsedRealtime() - mConnectAttempted));
+ long lastConnectAttempted = mConnectAttempted == -1 ? 0 : mConnectAttempted;
+ Log.d(
+ TAG,
+ "onUuidChanged: Time since last connect/manual disconnect="
+ + (SystemClock.elapsedRealtime() - lastConnectAttempted));
}
/*
* If a connect was attempted earlier without any UUID, we will do the connect now.
* Otherwise, allow the connect on UUID change.
*/
- if ((mConnectAttempted + timeout) > SystemClock.elapsedRealtime()) {
+ if (mConnectAttempted != -1
+ && (mConnectAttempted + timeout) > SystemClock.elapsedRealtime()) {
Log.d(TAG, "onUuidChanged: triggering connectDevice");
connectDevice();
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index c90ba8249d54..32d4580f67ec 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -464,5 +464,14 @@ public class GlobalSettingsValidators {
));
VALIDATORS.put(Global.HEARING_DEVICE_LOCAL_AMBIENT_VOLUME, ANY_STRING_VALIDATOR);
VALIDATORS.put(Global.HEARING_DEVICE_LOCAL_NOTIFICATION, ANY_STRING_VALIDATOR);
+ VALIDATORS.put(
+ Global.Wearable.WEAR_SYSTEM_STATUS_TRAY_CONFIGURATION,
+ new DiscreteValueValidator(
+ new String[] {
+ String.valueOf(
+ Global.Wearable.STATUS_TRAY_CONFIGURATION_DEFAULT),
+ String.valueOf(
+ Global.Wearable.STATUS_TRAY_CONFIGURATION_SYSTEM_HIDDEN)
+ }));
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 603a91195d04..03fea37deaf5 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -2434,28 +2434,40 @@ public class SettingsProvider extends ContentProvider {
context.checkCallingOrSelfPermission(
Manifest.permission.WRITE_DEVICE_CONFIG)
== PackageManager.PERMISSION_GRANTED;
- boolean isRoot = Binder.getCallingUid() == Process.ROOT_UID;
+ // Only the shell user and tests request the allowlist permission; this is used to force
+ // the WRITE_ALLOWLISTED_DEVICE_CONFIG path to log any flags that need to be allowlisted.
+ boolean isRestrictedShell = android.security.Flags.protectDeviceConfigFlags()
+ && hasAllowlistPermission;
- if (isRoot) {
- return;
- }
-
- if (hasWritePermission) {
+ if (!isRestrictedShell && hasWritePermission) {
assertCallingUserDenyList(flags);
} else if (hasAllowlistPermission) {
for (String flag : flags) {
boolean namespaceAllowed = false;
- for (String allowlistedPrefix : WritableNamespacePrefixes.ALLOWLIST) {
- if (flag.startsWith(allowlistedPrefix)) {
+ if (isRestrictedShell) {
+ int delimiterIndex = flag.indexOf("/");
+ String flagNamespace;
+ if (delimiterIndex != -1) {
+ flagNamespace = flag.substring(0, delimiterIndex);
+ } else {
+ flagNamespace = flag;
+ }
+ if (WritableNamespaces.ALLOWLIST.contains(flagNamespace)) {
namespaceAllowed = true;
- break;
+ }
+ } else {
+ for (String allowlistedPrefix : WritableNamespacePrefixes.ALLOWLIST) {
+ if (flag.startsWith(allowlistedPrefix)) {
+ namespaceAllowed = true;
+ break;
+ }
}
}
if (!namespaceAllowed && !DeviceConfig.getAdbWritableFlags().contains(flag)) {
- throw new SecurityException("Permission denial for flag '"
- + flag
- + "'; allowlist permission granted, but must add flag to the allowlist.");
+ Slog.wtf(LOG_TAG, "Permission denial for flag '" + flag
+ + "'; allowlist permission granted, but must add flag to the "
+ + "allowlist");
}
}
assertCallingUserDenyList(flags);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespaces.java b/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespaces.java
new file mode 100644
index 000000000000..d835c5f5c179
--- /dev/null
+++ b/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespaces.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 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.providers.settings;
+
+import android.util.ArraySet;
+
+import java.util.Arrays;
+import java.util.Set;
+
+/**
+ * Contains the list of namespaces in which any flag can be written by adb without root
+ * permissions.
+ * <p>
+ * A security review is required for any namespace that's added to this list. To add to
+ * the list, create a change and tag the OWNER. In the commit message, include a
+ * description of the flag's functionality, and a justification for why it needs to be
+ * allowlisted.
+ */
+final class WritableNamespaces {
+ public static final Set<String> ALLOWLIST =
+ new ArraySet<String>(Arrays.asList(
+ "exo"
+ ));
+}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 9de7faf04df6..a62b7fd3db81 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -635,7 +635,8 @@ public class SettingsBackupTest {
Settings.Global.Wearable.WEAR_MEDIA_SESSIONS_PACKAGE,
Settings.Global.Wearable.WEAR_POWER_ANOMALY_SERVICE_ENABLED,
Settings.Global.Wearable.CONNECTIVITY_KEEP_DATA_ON,
- Settings.Global.Wearable.PHONE_SWITCHING_REQUEST_SOURCE);
+ Settings.Global.Wearable.PHONE_SWITCHING_REQUEST_SOURCE,
+ Settings.Global.Wearable.WEAR_SYSTEM_STATUS_TRAY_CONFIGURATION);
private static final Set<String> BACKUP_DENY_LIST_SECURE_SETTINGS =
newHashSet(
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 526320debb1a..fef0f8c7857a 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -242,6 +242,8 @@
<uses-permission android:name="android.permission.READ_LOWPAN_CREDENTIAL" />
<uses-permission android:name="android.permission.BLUETOOTH_STACK" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
+ <uses-permission android:name="android.permission.COPY_ACCOUNTS" />
+ <uses-permission android:name="android.permission.REMOVE_ACCOUNTS" />
<uses-permission android:name="android.permission.RETRIEVE_WINDOW_TOKEN" />
<uses-permission android:name="android.permission.FRAME_STATS" />
<uses-permission android:name="android.permission.BIND_APPWIDGET" />
@@ -746,6 +748,9 @@
<!-- Permission required for ATS test - CarDevicePolicyManagerTest -->
<uses-permission android:name="android.permission.LOCK_DEVICE" />
+ <!-- Permission required for AuthenticationPolicyManagerTest -->
+ <uses-permission android:name="android.permission.MANAGE_SECURE_LOCK_DEVICE" />
+
<!-- Permissions required for CTS test - CtsSafetyCenterTestCases -->
<uses-permission android:name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
<uses-permission android:name="android.permission.READ_SAFETY_CENTER_STATUS" />
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 67666c3db81f..c82c63c7c78b 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1798,3 +1798,17 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "gsf_bouncer"
+ namespace: "systemui"
+ description: "Applies GSF font styles to Bouncer surfaces."
+ bug: "379364381"
+}
+
+flag {
+ name: "gsf_quick_settings"
+ namespace: "systemui"
+ description: "Applies GSF font styles to Quick Settings surfaces."
+ bug: "379364381"
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
index 9390664d1283..597cbf24729b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
@@ -31,6 +31,7 @@ import androidx.compose.ui.viewinterop.AndroidView
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.biometrics.AuthController
+import com.android.systemui.customization.R as customR
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
@@ -147,7 +148,7 @@ constructor(
} else {
val scaleFactor = authController.scaleFactor
val bottomPaddingPx =
- context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom)
+ context.resources.getDimensionPixelSize(customR.dimen.lock_icon_margin_bottom)
val heightPx = windowViewBounds.bottom.toFloat()
Pair(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index 160326792f81..0db545f262a9 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -519,8 +519,19 @@ internal class MultiPointerDraggableNode(
// we intercept an ongoing swipe transition (i.e. startDragImmediately() returned
// true).
if (overSlop == 0f) {
- val delta = (drag.position - consumablePointer.position).toFloat()
- check(delta != 0f) { "delta is equal to 0" }
+ // If the user drags in the opposite direction, the delta becomes zero because
+ // we return to the original point. Therefore, we should use the previous event
+ // to calculate the direction.
+ val delta = (drag.position - drag.previousPosition).toFloat()
+ check(delta != 0f) {
+ buildString {
+ append("delta is equal to 0 ")
+ append("touchSlop ${currentValueOf(LocalViewConfiguration).touchSlop} ")
+ append("consumablePointer.position ${consumablePointer.position} ")
+ append("drag.position ${drag.position} ")
+ append("drag.previousPosition ${drag.previousPosition}")
+ }
+ }
overSlop = delta.sign
}
drag
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index b3fd097946d0..4bccac1e3ba0 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -430,14 +430,6 @@ internal class MutableSceneTransitionLayoutStateImpl(
check(transitionStates.size == 1)
check(transitionStates[0] is TransitionState.Idle)
transitionStates = listOf(transition)
- } else if (currentState == transition.replacedTransition) {
- // Replace the transition.
- transitionStates =
- transitionStates.subList(0, transitionStates.lastIndex) + transition
-
- // Make sure it is removed from the finishedTransitions set if it was already
- // finished.
- finishedTransitions.remove(currentState)
} else {
// Append the new transition.
transitionStates = transitionStates + transition
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt
index b87cc5c88335..3622369b8ff9 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt
@@ -132,6 +132,9 @@ class InterruptionHandlerTest {
assertThat(state.currentTransitions)
.comparingElementsUsing(FromToCurrentTriple)
.containsExactly(
+ // Initial transition, A => B.
+ Triple(SceneA, SceneB, SceneB),
+
// Initial transition reversed, B back to A.
Triple(SceneA, SceneB, SceneA),
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
index 5ec74f8d2260..85d8b60d61fb 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
@@ -593,7 +593,7 @@ class MultiPointerDraggableTest {
}
}
- fun continueDraggingDown() {
+ fun dragDown() {
rule.onRoot().performTouchInput { moveBy(Offset(0f, touchSlop)) }
}
@@ -603,11 +603,78 @@ class MultiPointerDraggableTest {
assertThat(started).isFalse()
swipeConsume = true
- continueDraggingDown()
+ // Drag in same direction
+ dragDown()
assertThat(capturedChange).isNotNull()
capturedChange = null
- continueDraggingDown()
+ dragDown()
+ assertThat(capturedChange).isNull()
+
+ assertThat(started).isTrue()
+ }
+
+ @Test
+ fun multiPointerSwipeDetectorInteractionZeroOffsetFromStartPosition() {
+ val size = 200f
+ val middle = Offset(size / 2f, size / 2f)
+
+ var started = false
+
+ var capturedChange: PointerInputChange? = null
+ var swipeConsume = false
+
+ var touchSlop = 0f
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ Box(
+ Modifier.size(with(LocalDensity.current) { Size(size, size).toDpSize() })
+ .nestedScrollDispatcher()
+ .multiPointerDraggable(
+ orientation = Orientation.Vertical,
+ startDragImmediately = { false },
+ swipeDetector =
+ object : SwipeDetector {
+ override fun detectSwipe(change: PointerInputChange): Boolean {
+ capturedChange = change
+ return swipeConsume
+ }
+ },
+ onDragStarted = { _, _ ->
+ started = true
+ SimpleDragController(
+ onDrag = { /* do nothing */ },
+ onStop = { /* do nothing */ },
+ )
+ },
+ dispatcher = defaultDispatcher,
+ )
+ ) {}
+ }
+
+ fun startDraggingDown() {
+ rule.onRoot().performTouchInput {
+ down(middle)
+ moveBy(Offset(0f, touchSlop))
+ }
+ }
+
+ fun dragUp() {
+ rule.onRoot().performTouchInput { moveBy(Offset(0f, -touchSlop)) }
+ }
+
+ startDraggingDown()
+ assertThat(capturedChange).isNotNull()
+ capturedChange = null
+ assertThat(started).isFalse()
+
+ swipeConsume = true
+ // Drag in the opposite direction
+ dragUp()
+ assertThat(capturedChange).isNotNull()
+ capturedChange = null
+
+ dragUp()
assertThat(capturedChange).isNull()
assertThat(started).isTrue()
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/res/xml/network_security_config.xml b/packages/SystemUI/customization/res/values-land/dimens.xml
index 4bd9ca049f55..50f220c882bd 100644
--- a/libs/WindowManager/Shell/tests/e2e/mediaprojection/flicker-service/res/xml/network_security_config.xml
+++ b/packages/SystemUI/customization/res/values-land/dimens.xml
@@ -1,6 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 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.
@@ -15,8 +14,6 @@
~ limitations under the License.
-->
-<network-security-config>
- <domain-config cleartextTrafficPermitted="true">
- <domain includeSubdomains="true">localhost</domain>
- </domain-config>
-</network-security-config>
+<resources>
+ <dimen name="lock_icon_margin_bottom">24dp</dimen>
+</resources> \ No newline at end of file
diff --git a/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml
index 18073adf9e7a..876028195ff3 100644
--- a/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/customization/res/values-sw600dp-land/dimens.xml
@@ -17,4 +17,5 @@
<resources>
<dimen name="keyguard_smartspace_top_offset">0dp</dimen>
<dimen name="status_view_margin_horizontal">8dp</dimen>
+ <dimen name="lock_icon_margin_bottom">60dp</dimen>
</resources> \ No newline at end of file
diff --git a/packages/SystemUI/customization/res/values/dimens.xml b/packages/SystemUI/customization/res/values/dimens.xml
index 041ae62670e5..21b4c7165226 100644
--- a/packages/SystemUI/customization/res/values/dimens.xml
+++ b/packages/SystemUI/customization/res/values/dimens.xml
@@ -40,4 +40,5 @@
<dimen name="date_weather_view_height">24dp</dimen>
<dimen name="enhanced_smartspace_height">104dp</dimen>
<dimen name="status_view_margin_horizontal">0dp</dimen>
+ <dimen name="lock_icon_margin_bottom">74dp</dimen>
</resources> \ No newline at end of file
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
index 13f2c7212e36..4e64c50a3253 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
@@ -17,22 +17,23 @@
package com.android.systemui.biometrics.domain.interactor
import android.graphics.Rect
-import android.hardware.fingerprint.FingerprintManager
import android.view.MotionEvent
import android.view.Surface
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
+import com.android.systemui.biometrics.authController
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -44,29 +45,25 @@ import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class UdfpsOverlayInteractorTest : SysuiTestCase() {
@JvmField @Rule var mockitoRule = MockitoJUnit.rule()
- private lateinit var testScope: TestScope
+ private val kosmos = testKosmos()
- @Mock private lateinit var fingerprintManager: FingerprintManager
- @Mock private lateinit var authController: AuthController
+ private val testScope: TestScope = kosmos.testScope
+
+ private val authController: AuthController = kosmos.authController
@Captor private lateinit var authControllerCallback: ArgumentCaptor<AuthController.Callback>
@Mock private lateinit var udfpsOverlayParams: UdfpsOverlayParams
@Mock private lateinit var overlayBounds: Rect
- @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
private lateinit var underTest: UdfpsOverlayInteractor
- @Before
- fun setUp() {
- testScope = TestScope(StandardTestDispatcher())
- }
-
@Test
fun testShouldInterceptTouch() =
testScope.runTest {
@@ -107,15 +104,26 @@ class UdfpsOverlayInteractorTest : SysuiTestCase() {
assertThat(udfpsOverlayParams()).isEqualTo(firstParams)
}
+ @Test
+ fun testPaddingIsNeverNegative() =
+ testScope.runTest {
+ context.orCreateTestableResources.addOverride(R.dimen.pixel_pitch, 60.0f)
+ createUdfpsOverlayInteractor()
+ val padding by collectLastValue(underTest.iconPadding)
+ runCurrent()
+
+ verify(authController).addCallback(authControllerCallback.capture())
+
+ // Simulate the first empty udfps overlay params value.
+ authControllerCallback.value.onUdfpsLocationChanged(UdfpsOverlayParams())
+ runCurrent()
+
+ assertThat(padding).isEqualTo(0)
+ context.orCreateTestableResources.removeOverride(R.dimen.pixel_pitch)
+ }
+
private fun createUdfpsOverlayInteractor() {
- underTest =
- UdfpsOverlayInteractor(
- context,
- authController,
- selectedUserInteractor,
- fingerprintManager,
- testScope.backgroundScope
- )
+ underTest = kosmos.udfpsOverlayInteractor
testScope.runCurrent()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/development/data/repository/DevelopmentSettingRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/development/data/repository/DevelopmentSettingRepositoryTest.kt
new file mode 100644
index 000000000000..e17b66e90c2d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/development/data/repository/DevelopmentSettingRepositoryTest.kt
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2024 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.development.data.repository
+
+import android.content.pm.UserInfo
+import android.os.Build
+import android.os.UserHandle
+import android.os.UserManager
+import android.os.userManager
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.settings.fakeGlobalSettings
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.stub
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class DevelopmentSettingRepositoryTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ private val underTest = kosmos.developmentSettingRepository
+
+ @Test
+ fun nonAdminUser_unrestricted_neverDevelopmentEnabled() =
+ with(kosmos) {
+ testScope.runTest {
+ val userInfo = nonAdminUserInfo
+ val settingEnabled by
+ collectLastValue(underTest.isDevelopmentSettingEnabled(userInfo))
+
+ setUserRestriction(userInfo.userHandle, restricted = false)
+
+ assertThat(settingEnabled).isFalse()
+
+ setSettingValue(false)
+ assertThat(settingEnabled).isFalse()
+
+ setSettingValue(true)
+ assertThat(settingEnabled).isFalse()
+ }
+ }
+
+ @Test
+ fun nonAdminUser_restricted_neverDevelopmentEnabled() =
+ with(kosmos) {
+ testScope.runTest {
+ val userInfo = nonAdminUserInfo
+ val settingEnabled by
+ collectLastValue(underTest.isDevelopmentSettingEnabled(userInfo))
+
+ setUserRestriction(userInfo.userHandle, restricted = true)
+
+ assertThat(settingEnabled).isFalse()
+
+ setSettingValue(false)
+ assertThat(settingEnabled).isFalse()
+
+ setSettingValue(true)
+ assertThat(settingEnabled).isFalse()
+ }
+ }
+
+ @Test
+ fun adminUser_unrestricted_defaultValueOfSetting() =
+ with(kosmos) {
+ testScope.runTest {
+ val userInfo = adminUserInfo
+ val settingEnabled by
+ collectLastValue(underTest.isDevelopmentSettingEnabled(userInfo))
+
+ setUserRestriction(userInfo.userHandle, restricted = false)
+
+ val defaultValue = Build.TYPE == "eng"
+
+ assertThat(settingEnabled).isEqualTo(defaultValue)
+ }
+ }
+
+ @Test
+ fun adminUser_unrestricted_enabledTracksSetting() =
+ with(kosmos) {
+ testScope.runTest {
+ val userInfo = adminUserInfo
+ val settingEnabled by
+ collectLastValue(underTest.isDevelopmentSettingEnabled(userInfo))
+
+ setUserRestriction(userInfo.userHandle, restricted = false)
+
+ setSettingValue(false)
+ assertThat(settingEnabled).isFalse()
+
+ setSettingValue(true)
+ assertThat(settingEnabled).isTrue()
+ }
+ }
+
+ @Test
+ fun adminUser_restricted_neverDevelopmentEnabled() =
+ with(kosmos) {
+ testScope.runTest {
+ val userInfo = adminUserInfo
+ val settingEnabled by
+ collectLastValue(underTest.isDevelopmentSettingEnabled(userInfo))
+
+ setUserRestriction(userInfo.userHandle, restricted = true)
+
+ assertThat(settingEnabled).isFalse()
+
+ setSettingValue(false)
+ assertThat(settingEnabled).isFalse()
+
+ setSettingValue(true)
+ assertThat(settingEnabled).isFalse()
+ }
+ }
+
+ private companion object {
+ const val USER_RESTRICTION = UserManager.DISALLOW_DEBUGGING_FEATURES
+ const val SETTING_NAME = Settings.Global.DEVELOPMENT_SETTINGS_ENABLED
+
+ val adminUserInfo =
+ UserInfo(
+ /* id= */ 10,
+ /* name= */ "",
+ /* flags */ UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL,
+ )
+ val nonAdminUserInfo =
+ UserInfo(/* id= */ 11, /* name= */ "", /* flags */ UserInfo.FLAG_FULL)
+
+ fun Kosmos.setUserRestriction(userHandle: UserHandle, restricted: Boolean) {
+ userManager.stub {
+ on { hasUserRestrictionForUser(eq(USER_RESTRICTION), eq(userHandle)) } doReturn
+ restricted
+ }
+ }
+
+ fun Kosmos.setSettingValue(enabled: Boolean) {
+ fakeGlobalSettings.putInt(SETTING_NAME, if (enabled) 1 else 0)
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/development/domain/interactor/BuildNumberInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/development/domain/interactor/BuildNumberInteractorTest.kt
new file mode 100644
index 000000000000..f29dabe98664
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/development/domain/interactor/BuildNumberInteractorTest.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2024 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.development.domain.interactor
+
+import android.content.ClipData
+import android.content.ClipDescription
+import android.content.clipboardManager
+import android.content.pm.UserInfo
+import android.content.res.mainResources
+import android.os.Build
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.development.shared.model.BuildNumber
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.settings.fakeGlobalSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.verify
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class BuildNumberInteractorTest : SysuiTestCase() {
+
+ private val kosmos =
+ testKosmos().apply {
+ fakeUserRepository.setUserInfos(listOf(adminUserInfo, nonAdminUserInfo))
+ }
+
+ private val expectedBuildNumber =
+ BuildNumber(
+ kosmos.mainResources.getString(
+ R.string.bugreport_status,
+ Build.VERSION.RELEASE_OR_CODENAME,
+ Build.ID,
+ )
+ )
+
+ private val clipLabel =
+ kosmos.mainResources.getString(
+ com.android.systemui.res.R.string.build_number_clip_data_label
+ )
+
+ private val underTest = kosmos.buildNumberInteractor
+
+ @Test
+ fun nonAdminUser_settingEnabled_buildNumberNull() =
+ with(kosmos) {
+ testScope.runTest {
+ val buildNumber by collectLastValue(underTest.buildNumber)
+
+ fakeUserRepository.setSelectedUserInfo(nonAdminUserInfo)
+ setSettingValue(true)
+
+ assertThat(buildNumber).isNull()
+ }
+ }
+
+ @Test
+ fun adminUser_buildNumberCorrect_onlyWhenSettingEnabled() =
+ with(kosmos) {
+ testScope.runTest {
+ val buildNumber by collectLastValue(underTest.buildNumber)
+
+ fakeUserRepository.setSelectedUserInfo(adminUserInfo)
+
+ setSettingValue(false)
+ assertThat(buildNumber).isNull()
+
+ setSettingValue(true)
+ assertThat(buildNumber).isEqualTo(expectedBuildNumber)
+ }
+ }
+
+ @Test
+ fun copyToClipboard() =
+ with(kosmos) {
+ testScope.runTest {
+ fakeUserRepository.setSelectedUserInfo(adminUserInfo)
+
+ underTest.copyBuildNumber()
+ runCurrent()
+
+ val argumentCaptor = argumentCaptor<ClipData>()
+
+ verify(clipboardManager).setPrimaryClip(argumentCaptor.capture())
+
+ with(argumentCaptor.firstValue) {
+ assertThat(description.label).isEqualTo(clipLabel)
+ assertThat(description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN))
+ .isTrue()
+ assertThat(itemCount).isEqualTo(1)
+ assertThat(getItemAt(0).text).isEqualTo(expectedBuildNumber.value)
+ }
+ }
+ }
+
+ private companion object {
+ const val SETTING_NAME = Settings.Global.DEVELOPMENT_SETTINGS_ENABLED
+
+ val adminUserInfo =
+ UserInfo(
+ /* id= */ 10,
+ /* name= */ "",
+ /* flags */ UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL,
+ )
+ val nonAdminUserInfo =
+ UserInfo(/* id= */ 11, /* name= */ "", /* flags */ UserInfo.FLAG_FULL)
+
+ fun Kosmos.setSettingValue(enabled: Boolean) {
+ fakeGlobalSettings.putInt(SETTING_NAME, if (enabled) 1 else 0)
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
index f0d79bb83652..47cba0723804 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
@@ -568,6 +568,41 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() {
assertThat(isUnlocked).isFalse()
}
+ @Test
+ fun lockNow() =
+ testScope.runTest {
+ setLockAfterScreenTimeout(5000)
+ val isUnlocked by collectLastValue(underTest.deviceUnlockStatus.map { it.isUnlocked })
+ unlockDevice()
+ assertThat(isUnlocked).isTrue()
+
+ underTest.lockNow()
+ runCurrent()
+
+ assertThat(isUnlocked).isFalse()
+ }
+
+ @Test
+ fun deviceUnlockStatus_isResetToFalse_whenDeviceGoesToSleep_fromSleepButton() =
+ testScope.runTest {
+ setLockAfterScreenTimeout(5000)
+ kosmos.fakeAuthenticationRepository.powerButtonInstantlyLocks = false
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
+
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
+ assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+
+ kosmos.powerInteractor.setAsleepForTest(
+ sleepReason = PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON
+ )
+ runCurrent()
+
+ assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
+ }
+
private fun TestScope.unlockDevice() {
val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
index c39c3fe5f527..2d54337def13 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
@@ -36,6 +36,7 @@ import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.ui.viewmodel.fakeDeviceEntryIconViewModelTransition
import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -43,6 +44,7 @@ import kotlin.test.Test
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.After
import org.junit.Before
import org.junit.runner.RunWith
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
@@ -84,6 +86,12 @@ class UdfpsAccessibilityOverlayViewModelTest(flags: FlagsParameterization) : Sys
@Before
fun setup() {
underTest = kosmos.deviceEntryUdfpsAccessibilityOverlayViewModel
+ overrideResource(R.integer.udfps_padding_debounce_duration, 0)
+ }
+
+ @After
+ fun teardown() {
+ mContext.orCreateTestableResources.removeOverride(R.integer.udfps_padding_debounce_duration)
}
@Test
@@ -118,6 +126,7 @@ class UdfpsAccessibilityOverlayViewModelTest(flags: FlagsParameterization) : Sys
runCurrent()
assertThat(visible).isFalse()
}
+
fun fpNotRunning_overlayNotVisible() =
testScope.runTest {
val visible by collectLastValue(underTest.visible)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt
index cc4c7c419a11..27b8c59a076d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt
@@ -18,13 +18,17 @@ package com.android.systemui.keyboard.shortcut.data.repository
import android.content.Context
import android.content.Context.INPUT_SERVICE
-import android.hardware.input.InputGestureData
-import android.hardware.input.InputGestureData.createKeyTrigger
-import android.hardware.input.KeyGestureEvent
import android.hardware.input.fakeInputManager
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
-import android.view.KeyEvent
+import android.view.KeyEvent.KEYCODE_SLASH
+import android.view.KeyEvent.META_ALT_ON
+import android.view.KeyEvent.META_CAPS_LOCK_ON
+import android.view.KeyEvent.META_CTRL_ON
+import android.view.KeyEvent.META_FUNCTION_ON
+import android.view.KeyEvent.META_META_ON
+import android.view.KeyEvent.META_SHIFT_ON
+import android.view.KeyEvent.META_SYM_ON
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.hardware.input.Flags.FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES
@@ -32,17 +36,14 @@ import com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyboard.shortcut.customShortcutCategoriesRepository
-import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.AppCategories
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allCustomizableInputGesturesWithSimpleShortcutCombinations
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.customizableInputGestureWithUnknownKeyGestureType
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.expectedShortcutCategoriesWithSimpleShortcutCombination
+import com.android.systemui.keyboard.shortcut.shared.model.KeyCombination
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.settings.userTracker
import com.android.systemui.testKosmos
@@ -83,7 +84,7 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() {
whenever(
fakeInputManager.inputManager.getCustomInputGestures(/* filter= */ anyOrNull())
)
- .thenReturn(customizableInputGesturesWithSimpleShortcutCombinations)
+ .thenReturn(allCustomizableInputGesturesWithSimpleShortcutCombinations)
helper.toggle(deviceId = 123)
val categories by collectLastValue(repo.categories)
@@ -100,7 +101,7 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() {
whenever(
fakeInputManager.inputManager.getCustomInputGestures(/* filter= */ anyOrNull())
)
- .thenReturn(customizableInputGesturesWithSimpleShortcutCombinations)
+ .thenReturn(allCustomizableInputGesturesWithSimpleShortcutCombinations)
helper.toggle(deviceId = 123)
val categories by collectLastValue(repo.categories)
@@ -125,167 +126,73 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() {
}
}
- private fun simpleInputGestureData(
- keyCode: Int = KeyEvent.KEYCODE_A,
- modifiers: Int = KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON,
- keyGestureType: Int,
- ): InputGestureData {
- val builder = InputGestureData.Builder()
- builder.setKeyGestureType(keyGestureType)
- builder.setTrigger(createKeyTrigger(keyCode, modifiers))
- return builder.build()
+ @Test
+ fun pressedKeys_isEmptyByDefault() {
+ testScope.runTest {
+ val pressedKeys by collectLastValue(repo.pressedKeys)
+ assertThat(pressedKeys).isEmpty()
+
+ helper.toggle(deviceId = 123)
+ assertThat(pressedKeys).isEmpty()
+ }
}
- private fun simpleShortcutCategory(
- category: ShortcutCategoryType,
- subcategoryLabel: String,
- shortcutLabel: String,
- ): ShortcutCategory {
- return ShortcutCategory(
- type = category,
- subCategories =
- listOf(
- ShortcutSubCategory(
- label = subcategoryLabel,
- shortcuts = listOf(simpleShortcut(shortcutLabel)),
- )
- ),
- )
+ @Test
+ fun pressedKeys_recognizesAllSupportedModifiers() {
+ testScope.runTest {
+ helper.toggle(deviceId = 123)
+ val pressedKeys by collectLastValue(repo.pressedKeys)
+ repo.updateUserKeyCombination(
+ KeyCombination(modifiers = allSupportedModifiers, keyCode = null)
+ )
+
+ assertThat(pressedKeys)
+ .containsExactly(
+ ShortcutKey.Icon.ResIdIcon(R.drawable.ic_ksh_key_meta),
+ ShortcutKey.Text("Ctrl"),
+ ShortcutKey.Text("Fn"),
+ ShortcutKey.Text("Shift"),
+ ShortcutKey.Text("Alt"),
+ ShortcutKey.Text("Sym"),
+ )
+ }
}
- private fun simpleShortcut(label: String) =
- Shortcut(
- label = label,
- commands =
- listOf(
- ShortcutCommand(
- isCustom = true,
- keys =
- listOf(
- ShortcutKey.Text("Ctrl"),
- ShortcutKey.Text("Alt"),
- ShortcutKey.Text("A"),
- ),
- )
- ),
- )
+ @Test
+ fun pressedKeys_ignoresUnsupportedModifiers() {
+ testScope.runTest {
+ helper.toggle(deviceId = 123)
+ val pressedKeys by collectLastValue(repo.pressedKeys)
+ repo.updateUserKeyCombination(
+ KeyCombination(modifiers = META_CAPS_LOCK_ON, keyCode = null)
+ )
- private val customizableInputGestureWithUnknownKeyGestureType =
- // These key gesture events are currently not supported by shortcut helper customizer
- listOf(
- simpleInputGestureData(
- keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS
- ),
- simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_MEDIA_KEY),
- )
+ assertThat(pressedKeys).isEmpty()
+ }
+ }
- private val expectedShortcutCategoriesWithSimpleShortcutCombination =
- listOf(
- simpleShortcutCategory(System, "System apps", "Open assistant"),
- simpleShortcutCategory(System, "System controls", "Go to home screen"),
- simpleShortcutCategory(System, "System apps", "Open settings"),
- simpleShortcutCategory(System, "System controls", "Lock screen"),
- simpleShortcutCategory(System, "System controls", "View notifications"),
- simpleShortcutCategory(System, "System apps", "Take a note"),
- simpleShortcutCategory(System, "System controls", "Take screenshot"),
- simpleShortcutCategory(System, "System controls", "Go back"),
- simpleShortcutCategory(
- MultiTasking,
- "Split screen",
- "Switch from split screen to full screen",
- ),
- simpleShortcutCategory(
- MultiTasking,
- "Split screen",
- "Use split screen with current app on the left",
- ),
- simpleShortcutCategory(
- MultiTasking,
- "Split screen",
- "Switch to app on left or above while using split screen",
- ),
- simpleShortcutCategory(
- MultiTasking,
- "Split screen",
- "Use split screen with current app on the right",
- ),
- simpleShortcutCategory(
- MultiTasking,
- "Split screen",
- "Switch to app on right or below while using split screen",
- ),
- simpleShortcutCategory(System, "System controls", "Show shortcuts"),
- simpleShortcutCategory(System, "System controls", "View recent apps"),
- simpleShortcutCategory(AppCategories, "Applications", "Calculator"),
- simpleShortcutCategory(AppCategories, "Applications", "Calendar"),
- simpleShortcutCategory(AppCategories, "Applications", "Browser"),
- simpleShortcutCategory(AppCategories, "Applications", "Contacts"),
- simpleShortcutCategory(AppCategories, "Applications", "Email"),
- simpleShortcutCategory(AppCategories, "Applications", "Maps"),
- simpleShortcutCategory(AppCategories, "Applications", "SMS"),
- simpleShortcutCategory(MultiTasking, "Recent apps", "Cycle forward through recent apps"),
- )
+ @Test
+ fun pressedKeys_assertCorrectConversion() {
+ testScope.runTest {
+ helper.toggle(deviceId = 123)
+ val pressedKeys by collectLastValue(repo.pressedKeys)
+ repo.updateUserKeyCombination(
+ KeyCombination(modifiers = META_META_ON, keyCode = KEYCODE_SLASH)
+ )
+
+ assertThat(pressedKeys)
+ .containsExactly(
+ ShortcutKey.Icon.ResIdIcon(R.drawable.ic_ksh_key_meta),
+ ShortcutKey.Text("/"),
+ )
+ }
+ }
- private val customizableInputGesturesWithSimpleShortcutCombinations =
- listOf(
- simpleInputGestureData(
- keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT
- ),
- simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_HOME),
- simpleInputGestureData(
- keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS
- ),
- simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN),
- simpleInputGestureData(
- keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL
- ),
- simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES),
- simpleInputGestureData(
- keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT
- ),
- simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_BACK),
- simpleInputGestureData(
- keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION
- ),
- simpleInputGestureData(
- keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT
- ),
- simpleInputGestureData(
- keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT
- ),
- simpleInputGestureData(
- keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT
- ),
- simpleInputGestureData(
- keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT
- ),
- simpleInputGestureData(
- keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER
- ),
- simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS),
- simpleInputGestureData(
- keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR
- ),
- simpleInputGestureData(
- keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR
- ),
- simpleInputGestureData(
- keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER
- ),
- simpleInputGestureData(
- keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS
- ),
- simpleInputGestureData(
- keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL
- ),
- simpleInputGestureData(
- keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS
- ),
- simpleInputGestureData(
- keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING
- ),
- simpleInputGestureData(
- keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER
- ),
- )
+ private val allSupportedModifiers =
+ META_META_ON or
+ META_CTRL_ON or
+ META_FUNCTION_ON or
+ META_SHIFT_ON or
+ META_ALT_ON or
+ META_SYM_ON
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
index c9c39b3ebf66..a1e7ef4ac5a3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
@@ -16,11 +16,20 @@
package com.android.systemui.keyboard.shortcut.data.source
+import android.hardware.input.InputGestureData
+import android.hardware.input.InputGestureData.createKeyTrigger
+import android.hardware.input.KeyGestureEvent
import android.view.KeyEvent
import android.view.KeyboardShortcutGroup
import android.view.KeyboardShortcutInfo
+import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.AppCategories
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
import com.android.systemui.keyboard.shortcut.shared.model.shortcut
import com.android.systemui.res.R
@@ -64,6 +73,13 @@ object TestShortcuts {
}
}
+ private val goHomeShortcutInfo =
+ KeyboardShortcutInfo(
+ /* label = */ "Go to home screen",
+ /* keycode = */ KeyEvent.KEYCODE_B,
+ /* modifiers = */ KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON,
+ )
+
private val standardShortcutInfo1 =
KeyboardShortcutInfo(
/* label = */ "Standard shortcut 1",
@@ -79,6 +95,16 @@ object TestShortcuts {
}
}
+ private val customGoHomeShortcut =
+ shortcut("Go to home screen") {
+ command {
+ key("Ctrl")
+ key("Alt")
+ key("A")
+ isCustom(true)
+ }
+ }
+
private val standardShortcutInfo2 =
KeyboardShortcutInfo(
/* label = */ "Standard shortcut 2",
@@ -123,35 +149,38 @@ object TestShortcuts {
listOf(
shortcutInfoWithRepeatedLabel,
shortcutInfoWithRepeatedLabelAlternate,
- shortcutInfoWithRepeatedLabelSecondAlternate
- )
+ shortcutInfoWithRepeatedLabelSecondAlternate,
+ ),
)
private val subCategoryWithGroupedRepeatedShortcutLabels =
ShortcutSubCategory(
label = groupWithRepeatedShortcutLabels.label!!.toString(),
- shortcuts = listOf(shortcutWithGroupedRepeatedLabel)
+ shortcuts = listOf(shortcutWithGroupedRepeatedLabel),
)
private val groupWithStandardShortcutInfo =
KeyboardShortcutGroup("Standard group", listOf(standardShortcutInfo1))
+ val groupWithGoHomeShortcutInfo =
+ KeyboardShortcutGroup("System controls", listOf(goHomeShortcutInfo))
+
private val subCategoryWithStandardShortcut =
ShortcutSubCategory(
label = groupWithStandardShortcutInfo.label!!.toString(),
- shortcuts = listOf(standardShortcut1)
+ shortcuts = listOf(standardShortcut1),
)
private val groupWithOnlyUnsupportedModifierShortcut =
KeyboardShortcutGroup(
"Group with unsupported modifiers",
- listOf(shortcutInfoWithUnsupportedModifiers)
+ listOf(shortcutInfoWithUnsupportedModifiers),
)
private val groupWithSupportedAndUnsupportedModifierShortcut =
KeyboardShortcutGroup(
"Group with mix of supported and not supported modifiers",
- listOf(standardShortcutInfo3, shortcutInfoWithUnsupportedModifiers)
+ listOf(standardShortcutInfo3, shortcutInfoWithUnsupportedModifiers),
)
private val switchToNextLanguageShortcut =
@@ -174,19 +203,19 @@ object TestShortcuts {
private val subCategoryForInputLanguageSwitchShortcuts =
ShortcutSubCategory(
"Input",
- listOf(switchToNextLanguageShortcut, switchToPreviousLanguageShortcut)
+ listOf(switchToNextLanguageShortcut, switchToPreviousLanguageShortcut),
)
private val subCategoryWithUnsupportedShortcutsRemoved =
ShortcutSubCategory(
groupWithSupportedAndUnsupportedModifierShortcut.label!!.toString(),
- listOf(standardShortcut3)
+ listOf(standardShortcut3),
)
private val standardGroup1 =
KeyboardShortcutGroup(
"Standard group 1",
- listOf(standardShortcutInfo1, standardShortcutInfo2, standardShortcutInfo3)
+ listOf(standardShortcutInfo1, standardShortcutInfo2, standardShortcutInfo3),
)
private val standardPackageName1 = "standard.app.group1"
@@ -194,38 +223,63 @@ object TestShortcuts {
private val standardAppGroup1 =
KeyboardShortcutGroup(
"Standard app group 1",
- listOf(standardShortcutInfo1, standardShortcutInfo2, standardShortcutInfo3)
+ listOf(standardShortcutInfo1, standardShortcutInfo2, standardShortcutInfo3),
)
.apply { packageName = standardPackageName1 }
+ private val standardSystemAppSubcategoryWithCustomHomeShortcut =
+ ShortcutSubCategory("System controls", listOf(customGoHomeShortcut))
+
private val standardSubCategory1 =
ShortcutSubCategory(
standardGroup1.label!!.toString(),
- listOf(standardShortcut1, standardShortcut2, standardShortcut3)
+ listOf(standardShortcut1, standardShortcut2, standardShortcut3),
)
private val standardGroup2 =
KeyboardShortcutGroup(
"Standard group 2",
- listOf(standardShortcutInfo3, standardShortcutInfo2, standardShortcutInfo1)
+ listOf(standardShortcutInfo3, standardShortcutInfo2, standardShortcutInfo1),
)
private val standardSubCategory2 =
ShortcutSubCategory(
standardGroup2.label!!.toString(),
- listOf(standardShortcut3, standardShortcut2, standardShortcut1)
+ listOf(standardShortcut3, standardShortcut2, standardShortcut1),
)
private val standardGroup3 =
KeyboardShortcutGroup(
"Standard group 3",
- listOf(standardShortcutInfo2, standardShortcutInfo1)
+ listOf(standardShortcutInfo2, standardShortcutInfo1),
)
private val standardSubCategory3 =
ShortcutSubCategory(
standardGroup3.label!!.toString(),
- listOf(standardShortcut2, standardShortcut1)
+ listOf(standardShortcut2, standardShortcut1),
+ )
+
+ private val systemSubCategoryWithGoHomeShortcuts =
+ ShortcutSubCategory(
+ label = "System controls",
+ shortcuts =
+ listOf(
+ shortcut("Go to home screen") {
+ command {
+ key("Ctrl")
+ key("Alt")
+ key("B")
+ }
+ command {
+ key("Ctrl")
+ key("Alt")
+ key("A")
+ isCustom(true)
+ }
+ }
+ ),
)
+
val imeGroups = listOf(standardGroup1, standardGroup2, standardGroup3)
val imeCategory =
ShortcutCategory(
@@ -235,8 +289,8 @@ object TestShortcuts {
subCategoryForInputLanguageSwitchShortcuts,
standardSubCategory1,
standardSubCategory2,
- standardSubCategory3
- )
+ standardSubCategory3,
+ ),
)
val currentAppGroups = listOf(standardAppGroup1)
@@ -245,15 +299,33 @@ object TestShortcuts {
val systemGroups = listOf(standardGroup3, standardGroup2, standardGroup1)
val systemCategory =
ShortcutCategory(
- type = ShortcutCategoryType.System,
- subCategories = listOf(standardSubCategory3, standardSubCategory2, standardSubCategory1)
+ type = System,
+ subCategories = listOf(standardSubCategory3, standardSubCategory2, standardSubCategory1),
+ )
+
+ val systemCategoryWithMergedGoHomeShortcut =
+ ShortcutCategory(
+ type = System,
+ subCategories = listOf(systemSubCategoryWithGoHomeShortcuts),
+ )
+
+ val systemCategoryWithCustomHomeShortcut =
+ ShortcutCategory(
+ type = System,
+ subCategories =
+ listOf(
+ standardSubCategory3,
+ standardSubCategory2,
+ standardSubCategory1,
+ standardSystemAppSubcategoryWithCustomHomeShortcut,
+ ),
)
val multitaskingGroups = listOf(standardGroup2, standardGroup1)
val multitaskingCategory =
ShortcutCategory(
- type = ShortcutCategoryType.MultiTasking,
- subCategories = listOf(standardSubCategory2, standardSubCategory1)
+ type = MultiTasking,
+ subCategories = listOf(standardSubCategory2, standardSubCategory1),
)
val groupsWithDuplicateShortcutLabels =
@@ -266,14 +338,14 @@ object TestShortcuts {
listOf(
subCategoryForInputLanguageSwitchShortcuts,
subCategoryWithGroupedRepeatedShortcutLabels,
- subCategoryWithStandardShortcut
+ subCategoryWithStandardShortcut,
)
val groupsWithUnsupportedModifier =
listOf(
groupWithStandardShortcutInfo,
groupWithOnlyUnsupportedModifierShortcut,
- groupWithSupportedAndUnsupportedModifierShortcut
+ groupWithSupportedAndUnsupportedModifierShortcut,
)
val subCategoriesWithUnsupportedModifiersRemoved =
@@ -283,8 +355,174 @@ object TestShortcuts {
listOf(
subCategoryForInputLanguageSwitchShortcuts,
subCategoryWithStandardShortcut,
- subCategoryWithUnsupportedShortcutsRemoved
+ subCategoryWithUnsupportedShortcutsRemoved,
)
val groupsWithOnlyUnsupportedModifiers = listOf(groupWithOnlyUnsupportedModifierShortcut)
+
+ private fun simpleInputGestureData(
+ keyCode: Int = KeyEvent.KEYCODE_A,
+ modifiers: Int = KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON,
+ keyGestureType: Int,
+ ): InputGestureData {
+ val builder = InputGestureData.Builder()
+ builder.setKeyGestureType(keyGestureType)
+ builder.setTrigger(createKeyTrigger(keyCode, modifiers))
+ return builder.build()
+ }
+
+ private fun simpleShortcutCategory(
+ category: ShortcutCategoryType,
+ subcategoryLabel: String,
+ shortcutLabel: String,
+ ): ShortcutCategory {
+ return ShortcutCategory(
+ type = category,
+ subCategories =
+ listOf(
+ ShortcutSubCategory(
+ label = subcategoryLabel,
+ shortcuts = listOf(simpleShortcut(shortcutLabel)),
+ )
+ ),
+ )
+ }
+
+ private fun simpleShortcut(label: String) =
+ Shortcut(
+ label = label,
+ commands =
+ listOf(
+ ShortcutCommand(
+ isCustom = true,
+ keys =
+ listOf(
+ ShortcutKey.Text("Ctrl"),
+ ShortcutKey.Text("Alt"),
+ ShortcutKey.Text("A"),
+ ),
+ )
+ ),
+ )
+
+ val customizableInputGestureWithUnknownKeyGestureType =
+ // These key gesture events are currently not supported by shortcut helper customizer
+ listOf(
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS
+ ),
+ simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_MEDIA_KEY),
+ )
+
+ val expectedShortcutCategoriesWithSimpleShortcutCombination =
+ listOf(
+ simpleShortcutCategory(System, "System apps", "Open assistant"),
+ simpleShortcutCategory(System, "System controls", "Go to home screen"),
+ simpleShortcutCategory(System, "System apps", "Open settings"),
+ simpleShortcutCategory(System, "System controls", "Lock screen"),
+ simpleShortcutCategory(System, "System controls", "View notifications"),
+ simpleShortcutCategory(System, "System apps", "Take a note"),
+ simpleShortcutCategory(System, "System controls", "Take screenshot"),
+ simpleShortcutCategory(System, "System controls", "Go back"),
+ simpleShortcutCategory(
+ MultiTasking,
+ "Split screen",
+ "Switch from split screen to full screen",
+ ),
+ simpleShortcutCategory(
+ MultiTasking,
+ "Split screen",
+ "Use split screen with current app on the left",
+ ),
+ simpleShortcutCategory(
+ MultiTasking,
+ "Split screen",
+ "Switch to app on left or above while using split screen",
+ ),
+ simpleShortcutCategory(
+ MultiTasking,
+ "Split screen",
+ "Use split screen with current app on the right",
+ ),
+ simpleShortcutCategory(
+ MultiTasking,
+ "Split screen",
+ "Switch to app on right or below while using split screen",
+ ),
+ simpleShortcutCategory(System, "System controls", "Show shortcuts"),
+ simpleShortcutCategory(System, "System controls", "View recent apps"),
+ simpleShortcutCategory(AppCategories, "Applications", "Calculator"),
+ simpleShortcutCategory(AppCategories, "Applications", "Calendar"),
+ simpleShortcutCategory(AppCategories, "Applications", "Browser"),
+ simpleShortcutCategory(AppCategories, "Applications", "Contacts"),
+ simpleShortcutCategory(AppCategories, "Applications", "Email"),
+ simpleShortcutCategory(AppCategories, "Applications", "Maps"),
+ simpleShortcutCategory(AppCategories, "Applications", "SMS"),
+ simpleShortcutCategory(MultiTasking, "Recent apps", "Cycle forward through recent apps"),
+ )
+ val customInputGestureTypeHome =
+ simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_HOME)
+
+ val allCustomizableInputGesturesWithSimpleShortcutCombinations =
+ listOf(
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT
+ ),
+ simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_HOME),
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS
+ ),
+ simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN),
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL
+ ),
+ simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES),
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT
+ ),
+ simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_BACK),
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION
+ ),
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT
+ ),
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT
+ ),
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT
+ ),
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT
+ ),
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER
+ ),
+ simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS),
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR
+ ),
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR
+ ),
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER
+ ),
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS
+ ),
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL
+ ),
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS
+ ),
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING
+ ),
+ simpleInputGestureData(
+ keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER
+ ),
+ )
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
index 57c8b444b922..f7c77017e239 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
@@ -16,12 +16,24 @@
package com.android.systemui.keyboard.shortcut.domain.interactor
+import android.content.Context
+import android.content.Context.INPUT_SERVICE
+import android.hardware.input.InputGestureData
+import android.hardware.input.fakeInputManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyboard.shortcut.data.source.FakeKeyboardShortcutGroupsSource
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allCustomizableInputGesturesWithSimpleShortcutCombinations
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.customInputGestureTypeHome
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.groupWithGoHomeShortcutInfo
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.systemCategoryWithCustomHomeShortcut
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.systemCategoryWithMergedGoHomeShortcut
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.InputMethodEditor
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking
@@ -34,6 +46,8 @@ import com.android.systemui.keyboard.shortcut.shortcutHelperSystemShortcutsSourc
import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.settings.userTracker
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -42,13 +56,18 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
class ShortcutHelperCategoriesInteractorTest : SysuiTestCase() {
+ private val mockUserContext: Context = mock()
private val systemShortcutsSource = FakeKeyboardShortcutGroupsSource()
private val multitaskingShortcutsSource = FakeKeyboardShortcutGroupsSource()
+
@OptIn(ExperimentalCoroutinesApi::class)
private val kosmos =
testKosmos().also {
@@ -57,17 +76,23 @@ class ShortcutHelperCategoriesInteractorTest : SysuiTestCase() {
it.shortcutHelperMultiTaskingShortcutsSource = multitaskingShortcutsSource
it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource()
it.shortcutHelperCurrentAppShortcutsSource = FakeKeyboardShortcutGroupsSource()
+ it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockUserContext })
}
+ private val fakeInputManager = kosmos.fakeInputManager
private val testScope = kosmos.testScope
- private val interactor = kosmos.shortcutHelperCategoriesInteractor
+ private lateinit var interactor: ShortcutHelperCategoriesInteractor
private val helper = kosmos.shortcutHelperTestHelper
+ private val inter by lazy { kosmos.shortcutHelperCategoriesInteractor }
@Before
fun setShortcuts() {
+ interactor = kosmos.shortcutHelperCategoriesInteractor
helper.setImeShortcuts(TestShortcuts.imeGroups)
systemShortcutsSource.setGroups(TestShortcuts.systemGroups)
multitaskingShortcutsSource.setGroups(TestShortcuts.multitaskingGroups)
+ whenever(mockUserContext.getSystemService(INPUT_SERVICE))
+ .thenReturn(fakeInputManager.inputManager)
}
@Test
@@ -120,7 +145,7 @@ class ShortcutHelperCategoriesInteractorTest : SysuiTestCase() {
ShortcutCategory(
type = InputMethodEditor,
subCategories =
- TestShortcuts.imeSubCategoriesWithGroupedDuplicatedShortcutLabels
+ TestShortcuts.imeSubCategoriesWithGroupedDuplicatedShortcutLabels,
),
)
.inOrder()
@@ -140,7 +165,7 @@ class ShortcutHelperCategoriesInteractorTest : SysuiTestCase() {
ShortcutCategory(
type = System,
subCategories =
- TestShortcuts.subCategoriesWithGroupedDuplicatedShortcutLabels
+ TestShortcuts.subCategoriesWithGroupedDuplicatedShortcutLabels,
),
TestShortcuts.multitaskingCategory,
TestShortcuts.imeCategory,
@@ -163,7 +188,7 @@ class ShortcutHelperCategoriesInteractorTest : SysuiTestCase() {
ShortcutCategory(
type = MultiTasking,
subCategories =
- TestShortcuts.subCategoriesWithGroupedDuplicatedShortcutLabels
+ TestShortcuts.subCategoriesWithGroupedDuplicatedShortcutLabels,
),
TestShortcuts.imeCategory,
)
@@ -185,7 +210,7 @@ class ShortcutHelperCategoriesInteractorTest : SysuiTestCase() {
ShortcutCategory(
type = InputMethodEditor,
subCategories =
- TestShortcuts.imeSubCategoriesWithUnsupportedModifiersRemoved
+ TestShortcuts.imeSubCategoriesWithUnsupportedModifiersRemoved,
),
)
.inOrder()
@@ -203,7 +228,7 @@ class ShortcutHelperCategoriesInteractorTest : SysuiTestCase() {
.containsExactly(
ShortcutCategory(
type = System,
- subCategories = TestShortcuts.subCategoriesWithUnsupportedModifiersRemoved
+ subCategories = TestShortcuts.subCategoriesWithUnsupportedModifiersRemoved,
),
TestShortcuts.multitaskingCategory,
TestShortcuts.imeCategory,
@@ -224,7 +249,7 @@ class ShortcutHelperCategoriesInteractorTest : SysuiTestCase() {
TestShortcuts.systemCategory,
ShortcutCategory(
type = MultiTasking,
- subCategories = TestShortcuts.subCategoriesWithUnsupportedModifiersRemoved
+ subCategories = TestShortcuts.subCategoriesWithUnsupportedModifiersRemoved,
),
TestShortcuts.imeCategory,
)
@@ -240,10 +265,7 @@ class ShortcutHelperCategoriesInteractorTest : SysuiTestCase() {
helper.showFromActivity()
assertThat(categories)
- .containsExactly(
- TestShortcuts.multitaskingCategory,
- TestShortcuts.imeCategory,
- )
+ .containsExactly(TestShortcuts.multitaskingCategory, TestShortcuts.imeCategory)
.inOrder()
}
@@ -256,10 +278,63 @@ class ShortcutHelperCategoriesInteractorTest : SysuiTestCase() {
helper.showFromActivity()
assertThat(categories)
+ .containsExactly(TestShortcuts.systemCategory, TestShortcuts.imeCategory)
+ .inOrder()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_SHORTCUT_CUSTOMIZER)
+ fun categories_excludesCustomShortcutsWhenFlagIsOff() {
+ testScope.runTest {
+ setCustomInputGestures(allCustomizableInputGesturesWithSimpleShortcutCombinations)
+ helper.showFromActivity()
+ val categories by collectLastValue(interactor.shortcutCategories)
+ assertThat(categories)
.containsExactly(
TestShortcuts.systemCategory,
+ TestShortcuts.multitaskingCategory,
+ TestShortcuts.imeCategory,
+ )
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_SHORTCUT_CUSTOMIZER)
+ fun categories_includesCustomShortcutsWhenFlagIsOn() {
+ testScope.runTest {
+ setCustomInputGestures(listOf(customInputGestureTypeHome))
+ helper.showFromActivity()
+ val categories by collectLastValue(interactor.shortcutCategories)
+ assertThat(categories)
+ .containsExactly(
+ systemCategoryWithCustomHomeShortcut,
+ TestShortcuts.multitaskingCategory,
+ TestShortcuts.imeCategory,
+ )
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_SHORTCUT_CUSTOMIZER)
+ fun categories_correctlyMergesDefaultAndCustomShortcutsOfSameType() {
+ testScope.runTest {
+ setCustomInputGestures(listOf(customInputGestureTypeHome))
+ systemShortcutsSource.setGroups(groupWithGoHomeShortcutInfo)
+ helper.showFromActivity()
+
+ val categories by collectLastValue(interactor.shortcutCategories)
+
+ assertThat(categories)
+ .containsExactly(
+ systemCategoryWithMergedGoHomeShortcut,
+ TestShortcuts.multitaskingCategory,
TestShortcuts.imeCategory,
)
- .inOrder()
}
+ }
+
+ private fun setCustomInputGestures(customInputGestures: List<InputGestureData>) {
+ whenever(fakeInputManager.inputManager.getCustomInputGestures(/* filter= */ anyOrNull()))
+ .thenReturn(customInputGestures)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt
index 1580ea5c1272..000024f9b814 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt
@@ -16,6 +16,9 @@
package com.android.systemui.keyboard.shortcut.ui
+import android.content.Context
+import android.content.Context.INPUT_SERVICE
+import android.hardware.input.fakeInputManager
import androidx.test.annotation.UiThreadTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -36,6 +39,8 @@ import com.android.systemui.kosmos.testCase
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.activityStarter
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.settings.userTracker
import com.android.systemui.statusbar.phone.systemUIDialogFactory
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -44,6 +49,8 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -52,7 +59,7 @@ class ShortcutHelperDialogStarterTest : SysuiTestCase() {
private val fakeSystemSource = FakeKeyboardShortcutGroupsSource()
private val fakeMultiTaskingSource = FakeKeyboardShortcutGroupsSource()
-
+ private val mockUserContext: Context = mock()
private val kosmos =
Kosmos().also {
it.testCase = this
@@ -62,8 +69,10 @@ class ShortcutHelperDialogStarterTest : SysuiTestCase() {
it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource()
it.shortcutHelperInputShortcutsSource = FakeKeyboardShortcutGroupsSource()
it.shortcutHelperCurrentAppShortcutsSource = FakeKeyboardShortcutGroupsSource()
+ it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockUserContext })
}
+ private val inputManager = kosmos.fakeInputManager.inputManager
private val testScope = kosmos.testScope
private val testHelper = kosmos.shortcutHelperTestHelper
private val dialogFactory = kosmos.systemUIDialogFactory
@@ -85,6 +94,7 @@ class ShortcutHelperDialogStarterTest : SysuiTestCase() {
fun setUp() {
fakeSystemSource.setGroups(TestShortcuts.systemGroups)
fakeMultiTaskingSource.setGroups(TestShortcuts.multitaskingGroups)
+ whenever(mockUserContext.getSystemService(INPUT_SERVICE)).thenReturn(inputManager)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
index 92764ae94271..74a0bafda931 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
@@ -17,6 +17,10 @@
package com.android.systemui.keyguard.ui.binder
import android.app.IActivityTaskManager
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.CheckFlagsRule
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -25,8 +29,10 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardDismissTransition
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
+import com.android.window.flags.Flags
import com.android.wm.shell.keyguard.KeyguardTransitions
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.eq
@@ -41,6 +47,9 @@ import org.mockito.kotlin.any
@RunWith(AndroidJUnit4::class)
@kotlinx.coroutines.ExperimentalCoroutinesApi
class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() {
+
+ @get:Rule val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
private lateinit var underTest: WindowManagerLockscreenVisibilityManager
private lateinit var executor: FakeExecutor
@@ -68,32 +77,62 @@ class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() {
}
@Test
- fun testLockscreenVisible_andAodVisible() {
+ @RequiresFlagsDisabled(Flags.FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING)
+ fun testLockscreenVisible_andAodVisible_without_keyguard_shell_transitions() {
underTest.setLockscreenShown(true)
- underTest.setAodVisible(true)
-
verify(activityTaskManagerService).setLockScreenShown(true, false)
+ underTest.setAodVisible(true)
verify(activityTaskManagerService).setLockScreenShown(true, true)
+
verifyNoMoreInteractions(activityTaskManagerService)
}
@Test
- fun testGoingAway_whenLockscreenVisible_thenSurfaceMadeVisible() {
+ @RequiresFlagsEnabled(Flags.FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING)
+ fun testLockscreenVisible_andAodVisible_with_keyguard_shell_transitions() {
underTest.setLockscreenShown(true)
+ verify(keyguardTransitions).startKeyguardTransition(true, false)
underTest.setAodVisible(true)
+ verify(keyguardTransitions).startKeyguardTransition(true, true)
+ verifyNoMoreInteractions(keyguardTransitions)
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING)
+ fun testGoingAway_whenLockscreenVisible_thenSurfaceMadeVisible_without_keyguard_shell_transitions() {
+ underTest.setLockscreenShown(true)
verify(activityTaskManagerService).setLockScreenShown(true, false)
+ underTest.setAodVisible(true)
verify(activityTaskManagerService).setLockScreenShown(true, true)
+
verifyNoMoreInteractions(activityTaskManagerService)
underTest.setSurfaceBehindVisibility(true)
-
verify(activityTaskManagerService).keyguardGoingAway(anyInt())
+
verifyNoMoreInteractions(activityTaskManagerService)
}
@Test
- fun testSurfaceVisible_whenLockscreenNotShowing_doesNotTriggerGoingAway() {
+ @RequiresFlagsEnabled(Flags.FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING)
+ fun testGoingAway_whenLockscreenVisible_thenSurfaceMadeVisible_with_keyguard_shell_transitions() {
+ underTest.setLockscreenShown(true)
+ verify(keyguardTransitions).startKeyguardTransition(true, false)
+ underTest.setAodVisible(true)
+ verify(keyguardTransitions).startKeyguardTransition(true, true)
+
+ verifyNoMoreInteractions(keyguardTransitions)
+
+ underTest.setSurfaceBehindVisibility(true)
+ verify(keyguardTransitions).startKeyguardTransition(false, false)
+
+ verifyNoMoreInteractions(keyguardTransitions)
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING)
+ fun testSurfaceVisible_whenLockscreenNotShowing_doesNotTriggerGoingAway_without_keyguard_shell_transitions() {
underTest.setLockscreenShown(false)
underTest.setAodVisible(false)
@@ -106,7 +145,22 @@ class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() {
}
@Test
- fun testAodVisible_noLockscreenShownCallYet_doesNotShowLockscreenUntilLater() {
+ @RequiresFlagsEnabled(Flags.FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING)
+ fun testSurfaceVisible_whenLockscreenNotShowing_doesNotTriggerGoingAway_with_keyguard_shell_transitions() {
+ underTest.setLockscreenShown(false)
+ underTest.setAodVisible(false)
+
+ verify(keyguardTransitions).startKeyguardTransition(false, false)
+ verifyNoMoreInteractions(keyguardTransitions)
+
+ underTest.setSurfaceBehindVisibility(true)
+
+ verifyNoMoreInteractions(keyguardTransitions)
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING)
+ fun testAodVisible_noLockscreenShownCallYet_doesNotShowLockscreenUntilLater_without_keyguard_shell_transitions() {
underTest.setAodVisible(false)
verifyNoMoreInteractions(activityTaskManagerService)
@@ -116,7 +170,19 @@ class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() {
}
@Test
- fun setSurfaceBehindVisibility_goesAwayFirst_andIgnoresSecondCall() {
+ @RequiresFlagsEnabled(Flags.FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING)
+ fun testAodVisible_noLockscreenShownCallYet_doesNotShowLockscreenUntilLater_with_keyguard_shell_transitions() {
+ underTest.setAodVisible(false)
+ verifyNoMoreInteractions(keyguardTransitions)
+
+ underTest.setLockscreenShown(true)
+ verify(keyguardTransitions).startKeyguardTransition(true, false)
+ verifyNoMoreInteractions(activityTaskManagerService)
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING)
+ fun setSurfaceBehindVisibility_goesAwayFirst_andIgnoresSecondCall_without_keyguard_shell_transitions() {
underTest.setLockscreenShown(true)
underTest.setSurfaceBehindVisibility(true)
verify(activityTaskManagerService).keyguardGoingAway(0)
@@ -126,8 +192,27 @@ class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() {
}
@Test
- fun setSurfaceBehindVisibility_falseSetsLockscreenVisibility() {
+ @RequiresFlagsEnabled(Flags.FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING)
+ fun setSurfaceBehindVisibility_goesAwayFirst_andIgnoresSecondCall_with_keyguard_shell_transitions() {
+ underTest.setLockscreenShown(true)
+ underTest.setSurfaceBehindVisibility(true)
+ verify(keyguardTransitions).startKeyguardTransition(false, false)
+
+ underTest.setSurfaceBehindVisibility(true)
+ verifyNoMoreInteractions(keyguardTransitions)
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING)
+ fun setSurfaceBehindVisibility_falseSetsLockscreenVisibility_without_keyguard_shell_transitions() {
underTest.setSurfaceBehindVisibility(false)
verify(activityTaskManagerService).setLockScreenShown(eq(true), any())
}
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENSURE_KEYGUARD_DOES_TRANSITION_STARTING)
+ fun setSurfaceBehindVisibility_falseSetsLockscreenVisibility_with_keyguard_shell_transitions() {
+ underTest.setSurfaceBehindVisibility(false)
+ verify(keyguardTransitions).startKeyguardTransition(eq(true), any())
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
index d94c97af6f14..c0db95f9e5d2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
@@ -34,6 +34,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteract
import com.android.systemui.keyguard.domain.interactor.KeyguardSmartspaceInteractor
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
+import com.android.systemui.res.R
import com.android.systemui.shared.R as sharedR
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
import com.android.systemui.util.mockito.any
@@ -68,6 +69,7 @@ class SmartspaceSectionTest : SysuiTestCase() {
private val clockShouldBeCentered = MutableStateFlow(false)
private val hasCustomWeatherDataDisplay = MutableStateFlow(false)
private val isWeatherVisibleFlow = MutableStateFlow(false)
+ private val isShadeLayoutWide = MutableStateFlow(false)
@Before
fun setup() {
@@ -80,7 +82,7 @@ class SmartspaceSectionTest : SysuiTestCase() {
keyguardSmartspaceInteractor,
lockscreenSmartspaceController,
keyguardUnlockAnimationController,
- blueprintInteractor
+ blueprintInteractor,
)
constraintLayout = ConstraintLayout(mContext)
whenever(lockscreenSmartspaceController.buildAndConnectView(any()))
@@ -93,6 +95,7 @@ class SmartspaceSectionTest : SysuiTestCase() {
whenever(keyguardClockViewModel.clockShouldBeCentered).thenReturn(clockShouldBeCentered)
whenever(keyguardSmartspaceViewModel.isSmartspaceEnabled).thenReturn(true)
whenever(keyguardSmartspaceViewModel.isWeatherVisible).thenReturn(isWeatherVisibleFlow)
+ whenever(keyguardSmartspaceViewModel.isShadeLayoutWide).thenReturn(isShadeLayoutWide)
constraintSet = ConstraintSet()
}
@@ -125,6 +128,26 @@ class SmartspaceSectionTest : SysuiTestCase() {
}
@Test
+ fun testConstraintsWhenShadeLayoutIsNotWide() {
+ underTest.addViews(constraintLayout)
+ underTest.applyConstraints(constraintSet)
+
+ val smartspaceConstraints = constraintSet.getConstraint(smartspaceView.id)
+ assertThat(smartspaceConstraints.layout.endToEnd).isEqualTo(ConstraintSet.PARENT_ID)
+ }
+
+ @Test
+ fun testConstraintsWhenShadeLayoutIsWide() {
+ isShadeLayoutWide.value = true
+
+ underTest.addViews(constraintLayout)
+ underTest.applyConstraints(constraintSet)
+
+ val smartspaceConstraints = constraintSet.getConstraint(smartspaceView.id)
+ assertThat(smartspaceConstraints.layout.endToEnd).isEqualTo(R.id.split_shade_guideline)
+ }
+
+ @Test
fun testConstraintsWhenNotHasCustomWeatherDataDisplay() {
whenever(keyguardSmartspaceViewModel.isDateWeatherDecoupled).thenReturn(true)
underTest.addViews(constraintLayout)
@@ -160,6 +183,7 @@ class SmartspaceSectionTest : SysuiTestCase() {
assertThat(constraintSet.getVisibility(weatherView.id)).isEqualTo(GONE)
assertThat(constraintSet.getVisibility(dateView.id)).isEqualTo(VISIBLE)
}
+
@Test
fun testCustomDateWeatherVisibility() {
hasCustomWeatherDataDisplay.value = true
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModelTest.kt
index b0959e4eea0b..d42b538cf355 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModelTest.kt
@@ -27,10 +27,13 @@ import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepos
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -43,6 +46,16 @@ class DeviceEntryForegroundViewModelTest : SysuiTestCase() {
private val underTest: DeviceEntryForegroundViewModel =
kosmos.deviceEntryForegroundIconViewModel
+ @Before
+ fun setup() {
+ context.orCreateTestableResources.addOverride(R.integer.udfps_padding_debounce_duration, 0)
+ }
+
+ @After
+ fun teardown() {
+ context.orCreateTestableResources.removeOverride(R.integer.udfps_padding_debounce_duration)
+ }
+
@Test
fun aodIconColorWhite() =
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt
index 0c3fcb3ef759..adce9d65cbe0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt
@@ -26,6 +26,7 @@ import com.android.systemui.keyguard.data.repository.keyguardSmartspaceRepositor
import com.android.systemui.keyguard.shared.model.ClockSize
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -96,4 +97,26 @@ class KeyguardSmartspaceViewModelTest : SysuiTestCase() {
assertThat(isWeatherVisible).isEqualTo(false)
}
+
+ @Test
+ fun isShadeLayoutWide_withConfigTrue_true() =
+ with(kosmos) {
+ testScope.runTest {
+ val isShadeLayoutWide by collectLastValue(underTest.isShadeLayoutWide)
+ shadeRepository.setShadeLayoutWide(true)
+
+ assertThat(isShadeLayoutWide).isTrue()
+ }
+ }
+
+ @Test
+ fun isShadeLayoutWide_withConfigFalse_false() =
+ with(kosmos) {
+ testScope.runTest {
+ val isShadeLayoutWide by collectLastValue(underTest.isShadeLayoutWide)
+ shadeRepository.setShadeLayoutWide(false)
+
+ assertThat(isShadeLayoutWide).isFalse()
+ }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt
index 450aadd70171..ebc00c3897cb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt
@@ -20,6 +20,7 @@ import android.content.Context
import android.content.Intent
import android.graphics.drawable.Icon
import android.os.UserHandle
+import com.android.wm.shell.bubbles.Bubble
import com.android.wm.shell.bubbles.Bubbles
import java.util.Optional
import kotlinx.coroutines.CoroutineDispatcher
@@ -33,14 +34,29 @@ import kotlinx.coroutines.CoroutineDispatcher
class FakeNoteTaskBubbleController(
unUsed1: Context,
unsUsed2: CoroutineDispatcher,
- private val optionalBubbles: Optional<Bubbles>
+ private val optionalBubbles: Optional<Bubbles>,
) : NoteTaskBubblesController(unUsed1, unsUsed2) {
override suspend fun areBubblesAvailable() = optionalBubbles.isPresent
- override suspend fun showOrHideAppBubble(intent: Intent, userHandle: UserHandle, icon: Icon) {
+ override suspend fun showOrHideAppBubble(
+ intent: Intent,
+ userHandle: UserHandle,
+ icon: Icon,
+ bubbleExpandBehavior: NoteTaskBubbleExpandBehavior,
+ ) {
optionalBubbles.ifPresentOrElse(
- { bubbles -> bubbles.showOrHideAppBubble(intent, userHandle, icon) },
- { throw IllegalAccessException() }
+ { bubbles ->
+ if (
+ bubbleExpandBehavior == NoteTaskBubbleExpandBehavior.KEEP_IF_EXPANDED &&
+ bubbles.isBubbleExpanded(
+ Bubble.getAppBubbleKeyForApp(intent.`package`, userHandle)
+ )
+ ) {
+ return@ifPresentOrElse
+ }
+ bubbles.showOrHideAppBubble(intent, userHandle, icon)
+ },
+ { throw IllegalAccessException() },
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt
index 9ef6b9c13315..e55d6ad6c5a0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt
@@ -21,9 +21,9 @@ import android.graphics.drawable.Icon
import android.os.UserHandle
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.notetask.NoteTaskBubblesController.NoteTaskBubblesService
+import com.android.systemui.res.R
import com.android.wm.shell.bubbles.Bubbles
import com.google.common.truth.Truth.assertThat
import java.util.Optional
@@ -33,6 +33,9 @@ import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.never
+import org.mockito.kotlin.whenever
/** atest SystemUITests:NoteTaskBubblesServiceTest */
@SmallTest
@@ -61,12 +64,40 @@ internal class NoteTaskBubblesServiceTest : SysuiTestCase() {
}
@Test
- fun showOrHideAppBubble() {
+ fun showOrHideAppBubble_defaultExpandBehavior_shouldCallBubblesApi() {
val intent = Intent()
val user = UserHandle.SYSTEM
val icon = Icon.createWithResource(context, R.drawable.ic_note_task_shortcut_widget)
+ val bubbleExpandBehavior = NoteTaskBubbleExpandBehavior.DEFAULT
+ whenever(bubbles.isBubbleExpanded(any())).thenReturn(false)
+
+ createServiceBinder().showOrHideAppBubble(intent, user, icon, bubbleExpandBehavior)
+
+ verify(bubbles).showOrHideAppBubble(intent, user, icon)
+ }
+
+ @Test
+ fun showOrHideAppBubble_keepIfExpanded_bubbleShown_shouldNotCallBubblesApi() {
+ val intent = Intent().apply { setPackage("test") }
+ val user = UserHandle.SYSTEM
+ val icon = Icon.createWithResource(context, R.drawable.ic_note_task_shortcut_widget)
+ val bubbleExpandBehavior = NoteTaskBubbleExpandBehavior.KEEP_IF_EXPANDED
+ whenever(bubbles.isBubbleExpanded(any())).thenReturn(true)
+
+ createServiceBinder().showOrHideAppBubble(intent, user, icon, bubbleExpandBehavior)
+
+ verify(bubbles, never()).showOrHideAppBubble(intent, user, icon)
+ }
+
+ @Test
+ fun showOrHideAppBubble_keepIfExpanded_bubbleNotShown_shouldCallBubblesApi() {
+ val intent = Intent().apply { setPackage("test") }
+ val user = UserHandle.SYSTEM
+ val icon = Icon.createWithResource(context, R.drawable.ic_note_task_shortcut_widget)
+ val bubbleExpandBehavior = NoteTaskBubbleExpandBehavior.KEEP_IF_EXPANDED
+ whenever(bubbles.isBubbleExpanded(any())).thenReturn(false)
- createServiceBinder().showOrHideAppBubble(intent, user, icon)
+ createServiceBinder().showOrHideAppBubble(intent, user, icon, bubbleExpandBehavior)
verify(bubbles).showOrHideAppBubble(intent, user, icon)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt
index 8f4078b88fc0..d3578fb9f69b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt
@@ -19,6 +19,7 @@ import android.os.UserHandle
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.notetask.NoteTaskEntryPoint.QS_NOTES_TILE
import com.android.systemui.notetask.NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE
import com.google.common.truth.Truth.assertThat
import org.junit.Test
@@ -44,10 +45,19 @@ internal class NoteTaskInfoTest : SysuiTestCase() {
}
@Test
- fun launchMode_keyguardUnlocked_launchModeAppBubble() {
+ fun launchMode_keyguardUnlocked_launchModeAppBubble_withDefaultExpandBehavior() {
val underTest = DEFAULT_INFO.copy(isKeyguardLocked = false)
- assertThat(underTest.launchMode).isEqualTo(NoteTaskLaunchMode.AppBubble)
+ assertThat(underTest.launchMode)
+ .isEqualTo(NoteTaskLaunchMode.AppBubble(NoteTaskBubbleExpandBehavior.DEFAULT))
+ }
+
+ @Test
+ fun launchMode_keyguardUnlocked_qsTileEntryPoint_launchModeAppBubble_withKeepIfExpandedExpandBehavior() {
+ val underTest = DEFAULT_INFO.copy(isKeyguardLocked = false, entryPoint = QS_NOTES_TILE)
+
+ assertThat(underTest.launchMode)
+ .isEqualTo(NoteTaskLaunchMode.AppBubble(NoteTaskBubbleExpandBehavior.KEEP_IF_EXPANDED))
}
private companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index 83f95eaf4cda..e9633f49f76d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -21,9 +21,6 @@ import static com.android.systemui.flags.SceneContainerFlagParameterizationKt.pa
import static com.google.common.truth.Truth.assertThat;
-import static kotlinx.coroutines.flow.FlowKt.asStateFlow;
-import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
-
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -39,6 +36,9 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static kotlinx.coroutines.flow.FlowKt.asStateFlow;
+import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
+
import android.content.res.Configuration;
import android.content.res.Resources;
import android.platform.test.annotations.DisableFlags;
@@ -70,9 +70,6 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
import com.android.systemui.util.animation.DisappearParameters;
-import kotlinx.coroutines.flow.MutableStateFlow;
-import kotlinx.coroutines.flow.StateFlow;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -88,6 +85,8 @@ import java.util.List;
import javax.inject.Provider;
+import kotlinx.coroutines.flow.MutableStateFlow;
+import kotlinx.coroutines.flow.StateFlow;
import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
import platform.test.runner.parameterized.Parameters;
@@ -232,6 +231,7 @@ public class QSPanelControllerBaseTest extends SysuiTestCase {
@After
public void tearDown() {
+ mController.destroy();
disallowTestableLooperAsMainThread();
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index 02c5b5ad214c..96f6a622e2f3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -1,10 +1,10 @@
package com.android.systemui.qs
import android.content.res.Configuration
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
import android.testing.TestableResources
import android.view.ContextThemeWrapper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
@@ -37,8 +37,8 @@ import org.mockito.Mockito.any
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -81,37 +81,39 @@ class QSPanelControllerTest : SysuiTestCase() {
setShouldUseSplitShade(false)
whenever(qsPanel.resources).thenReturn(testableResources.resources)
whenever(qsPanel.context)
- .thenReturn( ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings))
+ .thenReturn(ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings))
whenever(qsPanel.getOrCreateTileLayout()).thenReturn(pagedTileLayout)
whenever(statusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false)
whenever(qsPanel.setListening(anyBoolean())).then {
whenever(qsPanel.isListening).thenReturn(it.getArgument(0))
}
- controller = QSPanelController(
- qsPanel,
- tunerService,
- qsHost,
- qsCustomizerController,
- /* usingMediaPlayer= */ usingMediaPlayer,
- mediaHost,
- qsTileRevealControllerFactory,
- dumpManager,
- metricsLogger,
- uiEventLogger,
- qsLogger,
- brightnessControllerFactory,
- brightnessSliderFactory,
- falsingManager,
- statusBarKeyguardViewManager,
- ResourcesSplitShadeStateController(),
- longPressEffectProvider,
- mediaCarouselInteractor,
- )
+ controller =
+ QSPanelController(
+ qsPanel,
+ tunerService,
+ qsHost,
+ qsCustomizerController,
+ /* usingMediaPlayer= */ usingMediaPlayer,
+ mediaHost,
+ qsTileRevealControllerFactory,
+ dumpManager,
+ metricsLogger,
+ uiEventLogger,
+ qsLogger,
+ brightnessControllerFactory,
+ brightnessSliderFactory,
+ falsingManager,
+ statusBarKeyguardViewManager,
+ ResourcesSplitShadeStateController(),
+ longPressEffectProvider,
+ mediaCarouselInteractor,
+ )
}
@After
fun tearDown() {
+ controller.destroy()
reset(mediaHost)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
index 369bb228494c..7880aceb53be 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -34,6 +34,7 @@ import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
import com.android.systemui.util.leak.RotationUtils
+import javax.inject.Provider
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -45,9 +46,8 @@ import org.mockito.Mockito.any
import org.mockito.Mockito.reset
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-import javax.inject.Provider
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -108,6 +108,7 @@ class QuickQSPanelControllerTest : SysuiTestCase() {
@After
fun tearDown() {
controller.onViewDetached()
+ controller.destroy()
}
@Test
@@ -184,7 +185,7 @@ class QuickQSPanelControllerTest : SysuiTestCase() {
dumpManager,
ResourcesSplitShadeStateController(),
longPressEffectProvider,
- mediaCarouselInteractor
+ mediaCarouselInteractor,
) {
private var rotation = RotationUtils.ROTATION_NONE
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 2e074da02103..48deee531bd9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -2532,6 +2532,64 @@ class SceneContainerStartableTest : SysuiTestCase() {
assertThat(isAlternateBouncerVisible).isFalse()
}
+ @Test
+ fun replacesLockscreenSceneOnBackStack_whenFaceUnlocked_fromShade_noAlternateBouncer() =
+ testScope.runTest {
+ val transitionState =
+ prepareState(
+ isDeviceUnlocked = false,
+ initialSceneKey = Scenes.Lockscreen,
+ authenticationMethod = AuthenticationMethodModel.Pin,
+ )
+ underTest.start()
+
+ val isUnlocked by
+ collectLastValue(
+ kosmos.deviceUnlockedInteractor.deviceUnlockStatus.map { it.isUnlocked }
+ )
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val backStack by collectLastValue(sceneBackInteractor.backStack)
+ val isAlternateBouncerVisible by
+ collectLastValue(kosmos.alternateBouncerInteractor.isVisible)
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ assertThat(isAlternateBouncerVisible).isFalse()
+
+ // Change to shade.
+ sceneInteractor.changeScene(Scenes.Shade, "")
+ transitionState.value = ObservableTransitionState.Idle(Scenes.Shade)
+ runCurrent()
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(Scenes.Shade)
+ assertThat(backStack?.asIterable()?.first()).isEqualTo(Scenes.Lockscreen)
+ assertThat(isAlternateBouncerVisible).isFalse()
+
+ // Show the alternate bouncer.
+ kosmos.alternateBouncerInteractor.forceShow()
+ kosmos.sysuiStatusBarStateController.leaveOpen = true // leave shade open
+ runCurrent()
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(Scenes.Shade)
+ assertThat(backStack?.asIterable()?.first()).isEqualTo(Scenes.Lockscreen)
+ assertThat(isAlternateBouncerVisible).isTrue()
+
+ // Simulate race condition by hiding the alternate bouncer *before* the face unlock:
+ kosmos.alternateBouncerInteractor.hide()
+ runCurrent()
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(Scenes.Shade)
+ assertThat(backStack?.asIterable()?.first()).isEqualTo(Scenes.Lockscreen)
+ assertThat(isAlternateBouncerVisible).isFalse()
+
+ // Trigger a face unlock.
+ updateFaceAuthStatus(isSuccess = true)
+ runCurrent()
+ assertThat(isUnlocked).isTrue()
+ assertThat(currentScene).isEqualTo(Scenes.Shade)
+ assertThat(backStack?.asIterable()?.first()).isEqualTo(Scenes.Gone)
+ assertThat(isAlternateBouncerVisible).isFalse()
+ }
+
private fun TestScope.emulateSceneTransition(
transitionStateFlow: MutableStateFlow<ObservableTransitionState>,
toScene: SceneKey,
@@ -2768,15 +2826,16 @@ class SceneContainerStartableTest : SysuiTestCase() {
}
private fun updateFaceAuthStatus(isSuccess: Boolean) {
- if (isSuccess) {
- kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(
- SuccessFaceAuthenticationStatus(
- successResult = Mockito.mock(FaceManager.AuthenticationResult::class.java)
- )
- )
- } else {
- kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus(
- FailedFaceAuthenticationStatus()
+ with(kosmos.fakeDeviceEntryFaceAuthRepository) {
+ isAuthenticated.value = isSuccess
+ setAuthenticationStatus(
+ if (isSuccess) {
+ SuccessFaceAuthenticationStatus(
+ successResult = Mockito.mock(FaceManager.AuthenticationResult::class.java)
+ )
+ } else {
+ FailedFaceAuthenticationStatus()
+ }
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
index fb7252b24295..60a185537b0d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar;
-import static android.app.Notification.CATEGORY_CALL;
-
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertEquals;
@@ -199,19 +197,6 @@ public class StatusBarIconViewTest extends SysuiTestCase {
}
@Test
- public void testContentDescForNotification_noNotifContent() {
- Notification n = new Notification.Builder(mContext, "test")
- .setSmallIcon(0)
- .setContentTitle("hello")
- .setCategory(CATEGORY_CALL)
- .build();
- assertThat(NotificationContentDescription.contentDescForNotification(mContext, n)
- .toString()).startsWith("com.android.systemui.tests notification");
- assertThat(NotificationContentDescription.contentDescForNotification(mContext, n)
- .toString()).doesNotContain("hello");
- }
-
- @Test
@EnableFlags({Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS})
public void setIcon_withPreloaded_usesPreloaded() {
Icon mockIcon = mock(Icon.class);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractorTest.kt
index 7c5a48728a11..3f995c69c32f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractorTest.kt
@@ -155,6 +155,30 @@ class VolumeDialogSlidersInteractorTest : SysuiTestCase() {
}
}
+ @Test
+ fun activeStreamChanges_showBoth() {
+ with(kosmos) {
+ testScope.runTest {
+ runCurrent()
+ fakeVolumeDialogController.updateState {
+ activeStream = AudioManager.STREAM_SYSTEM
+ states.put(AudioManager.STREAM_MUSIC, buildStreamState())
+ states.put(AudioManager.STREAM_SYSTEM, buildStreamState())
+ }
+ val slidersModel by collectLastValue(underTest.sliders)
+ runCurrent()
+
+ fakeVolumeDialogController.updateState { activeStream = AudioManager.STREAM_MUSIC }
+ runCurrent()
+
+ assertThat(slidersModel!!.slider)
+ .isEqualTo(VolumeDialogSliderType.Stream(AudioManager.STREAM_MUSIC))
+ assertThat(slidersModel!!.floatingSliders)
+ .containsExactly(VolumeDialogSliderType.Stream(AudioManager.STREAM_SYSTEM))
+ }
+ }
+ }
+
private fun buildStreamState(
build: VolumeDialogController.StreamState.() -> Unit = {}
): VolumeDialogController.StreamState {
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 235015b5286f..70ae5c1832af 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -91,7 +91,6 @@
<dimen name="notification_blocker_channel_list_height">128dp</dimen>
<dimen name="keyguard_indication_margin_bottom">8dp</dimen>
- <dimen name="lock_icon_margin_bottom">24dp</dimen>
<!-- Keyboard shortcuts helper -->
<dimen name="ksh_container_horizontal_margin">48dp</dimen>
diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
index 75bee9f9266a..055c3a641d1b 100644
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -20,7 +20,6 @@
<!-- keyguard-->
<dimen name="keyguard_indication_margin_bottom">25dp</dimen>
<dimen name="ambient_indication_margin_bottom">115dp</dimen>
- <dimen name="lock_icon_margin_bottom">60dp</dimen>
<!-- margin from keyguard status bar to clock. For split shade it should be
keyguard_split_shade_top_margin - status_bar_header_height_keyguard = 8dp -->
<dimen name="keyguard_clock_top_margin">8dp</dimen>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 580f6d302b37..8cab15506d20 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -948,7 +948,6 @@
<dimen name="keyguard_translate_distance_on_swipe_up">-200dp</dimen>
<dimen name="keyguard_indication_margin_bottom">32dp</dimen>
- <dimen name="lock_icon_margin_bottom">74dp</dimen>
<dimen name="ambient_indication_margin_bottom">71dp</dimen>
@@ -2057,6 +2056,9 @@
<!-- UDFPS view attributes -->
<!-- UDFPS icon size in microns/um -->
<dimen name="udfps_icon_size" format="float">6000</dimen>
+ <!-- Limits the updates to at most one update per debounce duration to avoid too many
+ updates due to quick changes to padding. -->
+ <integer name="udfps_padding_debounce_duration">100</integer>
<!-- Microns/ums (1000 um = 1mm) per pixel for the given device. If unspecified, UI that
relies on this value will not be sized correctly. -->
<item name="pixel_pitch" format="float" type="dimen">-1</item>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 245ba0aca876..c3f4222a5eb8 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3214,6 +3214,9 @@
<!-- Text to display when copying the build number off QS [CHAR LIMIT=NONE]-->
<string name="build_number_copy_toast">Build number copied to clipboard.</string>
+ <!-- Text for accessibility action for copying content to clipboard [CHAR LIMIT=NONE]-->
+ <string name="copy_to_clipboard_a11y_action">copy to clipboard.</string>
+
<!-- Status for conversation without interaction data [CHAR LIMIT=120] -->
<string name="basic_status">Open conversation</string>
<!--Title text for Conversation widget set up screen [CHAR LIMIT=180] -->
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
index abbbd730c47e..976329580c60 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
@@ -36,8 +36,10 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
+import kotlin.math.max
/** Encapsulates business logic for interacting with the UDFPS overlay. */
@SysUISingleton
@@ -124,8 +126,9 @@ constructor(
udfpsOverlayParams.map { params ->
val sensorWidth = params.nativeSensorBounds.right - params.nativeSensorBounds.left
val nativePadding = (sensorWidth - iconSize) / 2
- (nativePadding * params.scaleFactor).toInt()
- }
+ // padding can be negative when udfpsOverlayParams has not been initialized yet.
+ max(0, (nativePadding * params.scaleFactor).toInt())
+ }.distinctUntilChanged()
companion object {
private const val TAG = "UdfpsOverlayInteractor"
diff --git a/packages/SystemUI/src/com/android/systemui/development/data/repository/DevelopmentSettingRepository.kt b/packages/SystemUI/src/com/android/systemui/development/data/repository/DevelopmentSettingRepository.kt
new file mode 100644
index 000000000000..a8fa9797ebfd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/development/data/repository/DevelopmentSettingRepository.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 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.development.data.repository
+
+import android.content.pm.UserInfo
+import android.os.Build
+import android.os.UserManager
+import android.provider.Settings
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.kotlin.emitOnStart
+import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.withContext
+
+@SysUISingleton
+class DevelopmentSettingRepository
+@Inject
+constructor(
+ private val globalSettings: GlobalSettings,
+ private val userManager: UserManager,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+) {
+ private val settingFlow = globalSettings.observerFlow(SETTING)
+
+ /**
+ * Indicates whether development settings is enabled for this user. The conditions are:
+ * * Setting is enabled (defaults to true in eng builds)
+ * * User is an admin
+ * * User is not restricted from Debugging features.
+ */
+ fun isDevelopmentSettingEnabled(userInfo: UserInfo): Flow<Boolean> {
+ return settingFlow
+ .emitOnStart()
+ .map { checkDevelopmentSettingEnabled(userInfo) }
+ .flowOn(backgroundDispatcher)
+ }
+
+ private suspend fun checkDevelopmentSettingEnabled(userInfo: UserInfo): Boolean {
+ val hasUserRestriction =
+ withContext(backgroundDispatcher) {
+ userManager.hasUserRestrictionForUser(
+ UserManager.DISALLOW_DEBUGGING_FEATURES,
+ userInfo.userHandle,
+ )
+ }
+ val isSettingEnabled =
+ withContext(backgroundDispatcher) {
+ globalSettings.getInt(SETTING, DEFAULT_ENABLED) != 0
+ }
+ val isAdmin = userInfo.isAdmin
+ return isAdmin && !hasUserRestriction && isSettingEnabled
+ }
+
+ private companion object {
+ val DEFAULT_ENABLED = if (Build.TYPE == "eng") 1 else 0
+
+ const val SETTING = Settings.Global.DEVELOPMENT_SETTINGS_ENABLED
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/development/domain/interactor/BuildNumberInteractor.kt b/packages/SystemUI/src/com/android/systemui/development/domain/interactor/BuildNumberInteractor.kt
new file mode 100644
index 000000000000..4d786fe76bc1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/development/domain/interactor/BuildNumberInteractor.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2024 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.development.domain.interactor
+
+import android.content.ClipData
+import android.content.ClipboardManager
+import android.content.res.Resources
+import android.os.Build
+import android.os.UserHandle
+import com.android.internal.R as InternalR
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.development.data.repository.DevelopmentSettingRepository
+import com.android.systemui.development.shared.model.BuildNumber
+import com.android.systemui.res.R as SystemUIR
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.user.utils.UserScopedService
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flatMapConcat
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.withContext
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class BuildNumberInteractor
+@Inject
+constructor(
+ repository: DevelopmentSettingRepository,
+ @Main resources: Resources,
+ private val userRepository: UserRepository,
+ private val clipboardManagerProvider: UserScopedService<ClipboardManager>,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+) {
+
+ /**
+ * Build number, or `null` if Development Settings is not enabled for the current user.
+ *
+ * @see DevelopmentSettingRepository.isDevelopmentSettingEnabled
+ */
+ val buildNumber: Flow<BuildNumber?> =
+ userRepository.selectedUserInfo
+ .flatMapConcat { userInfo -> repository.isDevelopmentSettingEnabled(userInfo) }
+ .map { enabled -> buildText.takeIf { enabled } }
+
+ private val buildText =
+ BuildNumber(
+ resources.getString(
+ InternalR.string.bugreport_status,
+ Build.VERSION.RELEASE_OR_CODENAME,
+ Build.ID,
+ )
+ )
+
+ private val clipLabel = resources.getString(SystemUIR.string.build_number_clip_data_label)
+
+ private val currentUserHandle: UserHandle
+ get() = userRepository.getSelectedUserInfo().userHandle
+
+ /**
+ * Copy to the clipboard the build number for the current user.
+ *
+ * This can be performed regardless of the current user having Development Settings enabled
+ */
+ suspend fun copyBuildNumber() {
+ withContext(backgroundDispatcher) {
+ clipboardManagerProvider
+ .forUser(currentUserHandle)
+ .setPrimaryClip(ClipData.newPlainText(clipLabel, buildText.value))
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/development/shared/model/BuildNumber.kt b/packages/SystemUI/src/com/android/systemui/development/shared/model/BuildNumber.kt
new file mode 100644
index 000000000000..5bd713fc4840
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/development/shared/model/BuildNumber.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2024 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.development.shared.model
+
+@JvmInline value class BuildNumber(val value: String)
diff --git a/packages/SystemUI/src/com/android/systemui/development/ui/compose/BuildNumber.kt b/packages/SystemUI/src/com/android/systemui/development/ui/compose/BuildNumber.kt
new file mode 100644
index 000000000000..72e1cede0002
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/development/ui/compose/BuildNumber.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 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.development.ui.compose
+
+import androidx.compose.foundation.basicMarquee
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.wrapContentWidth
+import androidx.compose.material3.Text
+import androidx.compose.material3.minimumInteractiveComponentSize
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.hapticfeedback.HapticFeedbackType
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.platform.LocalHapticFeedback
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.onLongClick
+import androidx.compose.ui.semantics.semantics
+import com.android.systemui.communal.ui.compose.extensions.detectLongPressGesture
+import com.android.systemui.development.ui.viewmodel.BuildNumberViewModel
+import com.android.systemui.lifecycle.rememberViewModel
+import com.android.systemui.res.R
+
+@Composable
+fun BuildNumber(
+ viewModelFactory: BuildNumberViewModel.Factory,
+ textColor: Color,
+ modifier: Modifier = Modifier,
+) {
+ val viewModel = rememberViewModel(traceName = "BuildNumber") { viewModelFactory.create() }
+
+ val buildNumber = viewModel.buildNumber
+
+ if (buildNumber != null) {
+ val haptics = LocalHapticFeedback.current
+ val copyToClipboardActionLabel = stringResource(id = R.string.copy_to_clipboard_a11y_action)
+
+ Text(
+ text = buildNumber.value,
+ modifier =
+ modifier
+ .focusable()
+ .wrapContentWidth()
+ // Using this instead of combinedClickable because this node should not support
+ // single click
+ .pointerInput(Unit) {
+ detectLongPressGesture {
+ haptics.performHapticFeedback(HapticFeedbackType.LongPress)
+ viewModel.onBuildNumberLongPress()
+ }
+ }
+ .semantics {
+ onLongClick(copyToClipboardActionLabel) {
+ viewModel.onBuildNumberLongPress()
+ true
+ }
+ }
+ .basicMarquee(iterations = 1, initialDelayMillis = 2000)
+ .minimumInteractiveComponentSize(),
+ color = textColor,
+ maxLines = 1,
+ )
+ } else {
+ Spacer(modifier)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/development/ui/viewmodel/BuildNumberViewModel.kt b/packages/SystemUI/src/com/android/systemui/development/ui/viewmodel/BuildNumberViewModel.kt
new file mode 100644
index 000000000000..68c51ea80ffd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/development/ui/viewmodel/BuildNumberViewModel.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 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.development.ui.viewmodel
+
+import androidx.compose.runtime.getValue
+import com.android.systemui.development.domain.interactor.BuildNumberInteractor
+import com.android.systemui.development.shared.model.BuildNumber
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.receiveAsFlow
+import kotlinx.coroutines.launch
+
+/** View model for UI that (optionally) shows the build number and copies it on long press. */
+class BuildNumberViewModel
+@AssistedInject
+constructor(private val buildNumberInteractor: BuildNumberInteractor) : ExclusiveActivatable() {
+
+ private val hydrator = Hydrator("BuildNumberViewModel")
+
+ private val copyRequests = Channel<Unit>()
+
+ val buildNumber: BuildNumber? by
+ hydrator.hydratedStateOf(
+ traceName = "buildNumber",
+ initialValue = null,
+ source = buildNumberInteractor.buildNumber,
+ )
+
+ fun onBuildNumberLongPress() {
+ copyRequests.trySend(Unit)
+ }
+
+ override suspend fun onActivated(): Nothing {
+ coroutineScope {
+ launch { hydrator.activate() }
+ launch {
+ copyRequests.receiveAsFlow().collect { buildNumberInteractor.copyBuildNumber() }
+ }
+ awaitCancellation()
+ }
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(): BuildNumberViewModel
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index 1a5654144b65..400d09742d13 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.deviceentry.domain.interactor
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.policy.IKeyguardDismissCallback
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
@@ -43,7 +44,6 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
/**
* Hosts application business logic related to device entry.
@@ -174,6 +174,14 @@ constructor(
}
/**
+ * Whether lockscreen bypass is enabled. When enabled, the lockscreen will be automatically
+ * dismissed once the authentication challenge is completed. For example, completing a biometric
+ * authentication challenge via face unlock or fingerprint sensor can automatically bypass the
+ * lockscreen.
+ */
+ val isBypassEnabled: StateFlow<Boolean> = repository.isBypassEnabled
+
+ /**
* Attempt to enter the device and dismiss the lockscreen. If authentication is required to
* unlock the device it will transition to bouncer.
*
@@ -238,11 +246,8 @@ constructor(
isLockscreenEnabled()
}
- /**
- * Whether lockscreen bypass is enabled. When enabled, the lockscreen will be automatically
- * dismissed once the authentication challenge is completed. For example, completing a biometric
- * authentication challenge via face unlock or fingerprint sensor can automatically bypass the
- * lockscreen.
- */
- val isBypassEnabled: StateFlow<Boolean> = repository.isBypassEnabled
+ /** Locks the device instantly. */
+ fun lockNow() {
+ deviceUnlockedInteractor.lockNow()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
index 35eed5e6a6d9..7d684cab39f7 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
@@ -43,6 +43,7 @@ import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
@@ -57,6 +58,7 @@ import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch
@OptIn(ExperimentalCoroutinesApi::class)
@@ -178,6 +180,8 @@ constructor(
val deviceUnlockStatus: StateFlow<DeviceUnlockStatus> =
repository.deviceUnlockStatus.asStateFlow()
+ private val lockNowRequests = Channel<Unit>()
+
override suspend fun onActivated(): Nothing {
authenticationInteractor.authenticationMethod.collectLatest { authMethod ->
if (!authMethod.isSecure) {
@@ -196,6 +200,11 @@ constructor(
awaitCancellation()
}
+ /** Locks the device instantly. */
+ fun lockNow() {
+ lockNowRequests.trySend(Unit)
+ }
+
private suspend fun handleLockAndUnlockEvents() {
try {
Log.d(TAG, "started watching for lock and unlock events")
@@ -225,10 +234,12 @@ constructor(
.map { (isAsleep, lastSleepReason) ->
if (isAsleep) {
if (
- lastSleepReason == WakeSleepReason.POWER_BUTTON &&
+ (lastSleepReason == WakeSleepReason.POWER_BUTTON) &&
authenticationInteractor.getPowerButtonInstantlyLocks()
) {
LockImmediately("locked instantly from power button")
+ } else if (lastSleepReason == WakeSleepReason.SLEEP_BUTTON) {
+ LockImmediately("locked instantly from sleep button")
} else {
LockWithDelay("entering sleep")
}
@@ -256,6 +267,7 @@ constructor(
emptyFlow()
}
},
+ lockNowRequests.receiveAsFlow().map { LockImmediately("lockNow") },
)
.collectLatest(::onLockEvent)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt
index 7b3380a6a608..1af7340ad7b4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt
@@ -18,6 +18,9 @@ package com.android.systemui.keyboard.shortcut
import com.android.systemui.CoreStartable
import com.android.systemui.Flags.keyboardShortcutHelperRewrite
+import com.android.systemui.keyboard.shortcut.data.repository.CustomShortcutCategoriesRepository
+import com.android.systemui.keyboard.shortcut.data.repository.DefaultShortcutCategoriesRepository
+import com.android.systemui.keyboard.shortcut.data.repository.ShortcutCategoriesRepository
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperStateRepository
import com.android.systemui.keyboard.shortcut.data.source.AppCategoriesShortcutsSource
import com.android.systemui.keyboard.shortcut.data.source.CurrentAppShortcutsSource
@@ -27,6 +30,8 @@ import com.android.systemui.keyboard.shortcut.data.source.MultitaskingShortcutsS
import com.android.systemui.keyboard.shortcut.data.source.SystemShortcutsSource
import com.android.systemui.keyboard.shortcut.qualifiers.AppCategoriesShortcuts
import com.android.systemui.keyboard.shortcut.qualifiers.CurrentAppShortcuts
+import com.android.systemui.keyboard.shortcut.qualifiers.CustomShortcutCategories
+import com.android.systemui.keyboard.shortcut.qualifiers.DefaultShortcutCategories
import com.android.systemui.keyboard.shortcut.qualifiers.InputShortcuts
import com.android.systemui.keyboard.shortcut.qualifiers.MultitaskingShortcuts
import com.android.systemui.keyboard.shortcut.qualifiers.SystemShortcuts
@@ -63,6 +68,18 @@ interface ShortcutHelperModule {
impl: AppCategoriesShortcutsSource
): KeyboardShortcutGroupsSource
+ @Binds
+ @DefaultShortcutCategories
+ fun defaultShortcutCategoriesRepository(
+ impl: DefaultShortcutCategoriesRepository
+ ): ShortcutCategoriesRepository
+
+ @Binds
+ @CustomShortcutCategories
+ fun customShortcutCategoriesRepository(
+ impl: CustomShortcutCategoriesRepository
+ ): ShortcutCategoriesRepository
+
companion object {
@Provides
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
index ec1d358b6bd2..da5590ae27fa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
@@ -23,19 +23,24 @@ import android.hardware.input.InputGestureData.KeyTrigger
import android.hardware.input.InputManager
import android.hardware.input.InputSettings
import android.hardware.input.KeyGestureEvent
+import com.android.systemui.Flags.shortcutHelperKeyGlyph
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutGroup
import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutInfo
+import com.android.systemui.keyboard.shortcut.shared.model.KeyCombination
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Active
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
import com.android.systemui.settings.UserTracker
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
@@ -60,6 +65,8 @@ constructor(
private val inputManager: InputManager
get() = userContext.getSystemService(INPUT_SERVICE) as InputManager
+ private val _selectedKeyCombination = MutableStateFlow<KeyCombination?>(null)
+
private val activeInputDevice =
stateRepository.state.map {
if (it is Active) {
@@ -69,6 +76,41 @@ constructor(
}
}
+ val pressedKeys =
+ _selectedKeyCombination
+ .combine(activeInputDevice) { keyCombination, inputDevice ->
+ if (inputDevice == null || keyCombination == null) {
+ return@combine emptyList()
+ } else {
+ val keyGlyphMap =
+ if (shortcutHelperKeyGlyph()) {
+ inputManager.getKeyGlyphMap(inputDevice.id)
+ } else null
+ val modifiers =
+ shortcutCategoriesUtils.toShortcutModifierKeys(
+ keyCombination.modifiers,
+ keyGlyphMap,
+ )
+ val triggerKey =
+ keyCombination.keyCode?.let {
+ shortcutCategoriesUtils.toShortcutKey(
+ keyGlyphMap,
+ inputDevice.keyCharacterMap,
+ keyCode = it,
+ )
+ }
+ val keys = mutableListOf<ShortcutKey>()
+ modifiers?.let { keys += it }
+ triggerKey?.let { keys += it }
+ return@combine keys
+ }
+ }
+ .stateIn(
+ scope = backgroundScope,
+ started = SharingStarted.Lazily,
+ initialValue = emptyList(),
+ )
+
override val categories: Flow<List<ShortcutCategory>> =
activeInputDevice
.map { inputDevice ->
@@ -104,6 +146,10 @@ constructor(
started = SharingStarted.Lazily,
)
+ fun updateUserKeyCombination(keyCombination: KeyCombination?) {
+ _selectedKeyCombination.value = keyCombination
+ }
+
private fun toInternalGroupSources(
inputGestures: List<InputGestureData>
): List<InternalGroupsSource> {
@@ -148,7 +194,7 @@ constructor(
private fun fetchGroupLabelByGestureType(
@KeyGestureEvent.KeyGestureType keyGestureType: Int
): String? {
- InputGestures.gestureToInternalKeyboardShortcutGroupLabelMap[keyGestureType]?.let {
+ InputGestures.gestureToInternalKeyboardShortcutGroupLabelResIdMap[keyGestureType]?.let {
return context.getString(it)
} ?: return null
}
@@ -156,7 +202,7 @@ constructor(
private fun fetchShortcutInfoLabelByGestureType(
@KeyGestureEvent.KeyGestureType keyGestureType: Int
): String? {
- InputGestures.gestureToInternalKeyboardShortcutInfoLabelMap[keyGestureType]?.let {
+ InputGestures.gestureToInternalKeyboardShortcutInfoLabelResIdMap[keyGestureType]?.let {
return context.getString(it)
} ?: return null
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestures.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestures.kt
index 90be9888e622..7bb294df98cd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestures.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestures.kt
@@ -81,7 +81,7 @@ object InputGestures {
KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING to AppCategories,
)
- val gestureToInternalKeyboardShortcutGroupLabelMap =
+ val gestureToInternalKeyboardShortcutGroupLabelResIdMap =
mapOf(
// System Category
KEY_GESTURE_TYPE_HOME to R.string.shortcut_helper_category_system_controls,
@@ -129,7 +129,7 @@ object InputGestures {
R.string.keyboard_shortcut_group_applications,
)
- val gestureToInternalKeyboardShortcutInfoLabelMap =
+ val gestureToInternalKeyboardShortcutInfoLabelResIdMap =
mapOf(
// System Category
KEY_GESTURE_TYPE_HOME to R.string.group_system_access_home_screen,
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt
index 899fd15d6115..3988d1f155bd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt
@@ -24,6 +24,7 @@ import android.util.Log
import android.view.InputDevice
import android.view.KeyCharacterMap
import android.view.KeyEvent
+import android.view.KeyEvent.META_META_ON
import com.android.systemui.Flags.shortcutHelperKeyGlyph
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutGroup
@@ -92,7 +93,7 @@ constructor(
}
.filter { it.shortcuts.isNotEmpty() }
return if (subCategories.isEmpty()) {
- Log.wtf(TAG, "Empty sub categories after converting $shortcutGroups")
+ Log.w(TAG, "Empty sub categories after converting $shortcutGroups")
null
} else {
ShortcutCategory(type, subCategories)
@@ -161,7 +162,7 @@ constructor(
}
if (remainingModifiers != 0) {
// There is a remaining modifier we don't support
- Log.wtf(TAG, "Unsupported modifiers remaining: $remainingModifiers")
+ Log.w(TAG, "Unsupported modifiers remaining: $remainingModifiers")
return null
}
if (info.keycode != 0 || info.baseCharacter > Char.MIN_VALUE) {
@@ -170,21 +171,32 @@ constructor(
?: return null
}
if (keys.isEmpty()) {
- Log.wtf(TAG, "No keys for $info")
+ Log.w(TAG, "No keys for $info")
return null
}
return ShortcutCommand(keys = keys, isCustom = info.isCustomShortcut)
}
+ fun toShortcutModifierKeys(modifiers: Int, keyGlyphMap: KeyGlyphMap?): List<ShortcutKey>? {
+ val keys: MutableList<ShortcutKey> = mutableListOf()
+ var remainingModifiers = modifiers
+ SUPPORTED_MODIFIERS.forEach { supportedModifier ->
+ if ((supportedModifier and remainingModifiers) != 0) {
+ keys += toShortcutModifierKey(keyGlyphMap, supportedModifier) ?: return null
+ remainingModifiers = remainingModifiers and supportedModifier.inv()
+ }
+ }
+ return keys
+ }
+
private fun toShortcutModifierKey(keyGlyphMap: KeyGlyphMap?, modifierMask: Int): ShortcutKey? {
val modifierDrawable = keyGlyphMap?.getDrawableForModifierState(context, modifierMask)
if (modifierDrawable != null) {
return ShortcutKey.Icon.DrawableIcon(drawable = modifierDrawable)
}
- val iconResId = ShortcutHelperKeys.keyIcons[modifierMask]
- if (iconResId != null) {
- return ShortcutKey.Icon.ResIdIcon(iconResId)
+ if (modifierMask == META_META_ON) {
+ return ShortcutKey.Icon.ResIdIcon(ShortcutHelperKeys.metaModifierIconResId)
}
val modifierLabel = ShortcutHelperKeys.modifierLabels[modifierMask]
@@ -195,7 +207,7 @@ constructor(
return null
}
- private fun toShortcutKey(
+ fun toShortcutKey(
keyGlyphMap: KeyGlyphMap?,
keyCharacterMap: KeyCharacterMap,
keyCode: Int,
@@ -222,7 +234,7 @@ constructor(
if (displayLabelCharacter.code != 0) {
return ShortcutKey.Text(displayLabelCharacter.toString())
}
- Log.wtf(TAG, "Couldn't find label or icon for key: $keyCode")
+ Log.w(TAG, "Couldn't find label or icon for key: $keyCode")
return null
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt
index 288efa275219..e47b33f8c37c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperKeys.kt
@@ -116,9 +116,10 @@ import com.android.systemui.res.R
object ShortcutHelperKeys {
+ val metaModifierIconResId = R.drawable.ic_ksh_key_meta
+
val keyIcons =
mapOf(
- META_META_ON to R.drawable.ic_ksh_key_meta,
KEYCODE_BACK to R.drawable.ic_arrow_back_2,
KEYCODE_HOME to R.drawable.ic_radio_button_unchecked,
KEYCODE_RECENT_APPS to R.drawable.ic_check_box_outline_blank,
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt
index 85d22144f201..aad55dc11c16 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt
@@ -16,13 +16,22 @@
package com.android.systemui.keyboard.shortcut.domain.interactor
-import android.view.KeyEvent.META_META_ON
+import com.android.systemui.keyboard.shortcut.data.repository.CustomShortcutCategoriesRepository
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperKeys
+import com.android.systemui.keyboard.shortcut.shared.model.KeyCombination
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
import javax.inject.Inject
-class ShortcutCustomizationInteractor @Inject constructor() {
+class ShortcutCustomizationInteractor
+@Inject
+constructor(private val customShortcutRepository: CustomShortcutCategoriesRepository) {
+ val pressedKeys = customShortcutRepository.pressedKeys
+
+ fun updateUserSelectedKeyCombination(keyCombination: KeyCombination?) {
+ customShortcutRepository.updateUserKeyCombination(keyCombination)
+ }
+
fun getDefaultCustomShortcutModifierKey(): ShortcutKey.Icon.ResIdIcon {
- return ShortcutKey.Icon.ResIdIcon(ShortcutHelperKeys.keyIcons[META_META_ON]!!)
+ return ShortcutKey.Icon.ResIdIcon(ShortcutHelperKeys.metaModifierIconResId)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt
index 39fc27d35082..0381eaec5026 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt
@@ -16,37 +16,66 @@
package com.android.systemui.keyboard.shortcut.domain.interactor
+import com.android.systemui.Flags.keyboardShortcutHelperShortcutCustomizer
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyboard.shortcut.data.repository.DefaultShortcutCategoriesRepository
+import com.android.systemui.keyboard.shortcut.data.repository.ShortcutCategoriesRepository
+import com.android.systemui.keyboard.shortcut.qualifiers.CustomShortcutCategories
+import com.android.systemui.keyboard.shortcut.qualifiers.DefaultShortcutCategories
import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
+import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOf
@SysUISingleton
class ShortcutHelperCategoriesInteractor
@Inject
-constructor(categoriesRepository: DefaultShortcutCategoriesRepository) {
-
+constructor(
+ @DefaultShortcutCategories defaultCategoriesRepository: ShortcutCategoriesRepository,
+ @CustomShortcutCategories customCategoriesRepositoryLazy: Lazy<ShortcutCategoriesRepository>,
+) {
val shortcutCategories: Flow<List<ShortcutCategory>> =
- categoriesRepository.categories.map { categories ->
- categories.map { category -> groupSubCategoriesInCategory(category) }
+ defaultCategoriesRepository.categories.combine(
+ if (keyboardShortcutHelperShortcutCustomizer()) {
+ customCategoriesRepositoryLazy.get().categories
+ } else {
+ flowOf(emptyList())
+ }
+ ) { defaultShortcutCategories, customShortcutCategories ->
+ groupCategories(defaultShortcutCategories + customShortcutCategories)
}
- private fun groupSubCategoriesInCategory(shortcutCategory: ShortcutCategory): ShortcutCategory {
- val subCategoriesWithGroupedShortcuts =
- shortcutCategory.subCategories.map {
+ private fun groupCategories(
+ shortcutCategories: List<ShortcutCategory>
+ ): List<ShortcutCategory> {
+ return shortcutCategories
+ .groupBy { it.type }
+ .entries
+ .map { (categoryType, groupedCategories) ->
+ ShortcutCategory(
+ type = categoryType,
+ subCategories =
+ groupSubCategories(groupedCategories.flatMap { it.subCategories }),
+ )
+ }
+ }
+
+ private fun groupSubCategories(
+ subCategories: List<ShortcutSubCategory>
+ ): List<ShortcutSubCategory> {
+ return subCategories
+ .groupBy { it.label }
+ .entries
+ .map { (label, groupedSubcategories) ->
ShortcutSubCategory(
- label = it.label,
- shortcuts = groupShortcutsInSubcategory(it.shortcuts),
+ label = label,
+ shortcuts =
+ groupShortcutsInSubcategory(groupedSubcategories.flatMap { it.shortcuts }),
)
}
- return ShortcutCategory(
- type = shortcutCategory.type,
- subCategories = subCategoriesWithGroupedShortcuts,
- )
}
private fun groupShortcutsInSubcategory(shortcuts: List<Shortcut>) =
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/qualifiers/CustomShortcutCategories.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/qualifiers/CustomShortcutCategories.kt
new file mode 100644
index 000000000000..8acb9eebbd4d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/qualifiers/CustomShortcutCategories.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 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.keyboard.shortcut.qualifiers
+
+import javax.inject.Qualifier
+
+@Qualifier annotation class CustomShortcutCategories
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/qualifiers/DefaultShortcutCategories.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/qualifiers/DefaultShortcutCategories.kt
new file mode 100644
index 000000000000..f94e10d17964
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/qualifiers/DefaultShortcutCategories.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 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.keyboard.shortcut.qualifiers
+
+import javax.inject.Qualifier
+
+@Qualifier annotation class DefaultShortcutCategories
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/KeyCombination.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/KeyCombination.kt
new file mode 100644
index 000000000000..5e4031b29502
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/KeyCombination.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2024 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.keyboard.shortcut.shared.model
+
+data class KeyCombination(val modifiers: Int, val keyCode: Int?)
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
index 912bfe99cea8..08fd0af81006 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
@@ -123,7 +123,7 @@ constructor(
userContext.packageManager.getApplicationIcon(type.packageName)
IconSource(painter = DrawablePainter(drawable = iconDrawable))
} catch (e: NameNotFoundException) {
- Log.wtf(
+ Log.w(
"ShortcutHelperViewModel",
"Package not found when retrieving icon for ${type.packageName}",
)
@@ -153,7 +153,7 @@ constructor(
packageManagerForUser.getApplicationInfo(type.packageName, /* flags= */ 0)
return packageManagerForUser.getApplicationLabel(currentAppInfo).toString()
} catch (e: NameNotFoundException) {
- Log.wtf(
+ Log.w(
"ShortcutHelperViewModel",
"Package Not found when retrieving Label for ${type.packageName}",
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 32c2bc7c8620..7097c1d2fc4e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -662,7 +662,9 @@ public class KeyguardService extends Service {
trace("doKeyguardTimeout");
checkPermission();
- if (KeyguardWmStateRefactor.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
+ mDeviceEntryInteractorLazy.get().lockNow();
+ } else if (KeyguardWmStateRefactor.isEnabled()) {
mKeyguardLockWhileAwakeInteractor.onKeyguardServiceDoKeyguardTimeout(options);
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
index b51bb7b229ee..aa7eb2933992 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
@@ -28,6 +28,7 @@ import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import com.android.systemui.biometrics.AuthController
+import com.android.systemui.customization.R as customR
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
@@ -115,7 +116,7 @@ constructor(
val scaleFactor: Float = authController.scaleFactor
val mBottomPaddingPx =
- context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom)
+ context.resources.getDimensionPixelSize(customR.dimen.lock_icon_margin_bottom)
val bounds = windowManager.currentWindowMetrics.bounds
var widthPixels = bounds.right.toFloat()
if (featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE)) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
index 7ad2ec5e3bf8..d54d411b7de6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
@@ -124,7 +124,7 @@ constructor(
ConstraintSet.START,
ConstraintSet.PARENT_ID,
ConstraintSet.START,
- horizontalPaddingStart
+ horizontalPaddingStart,
)
// migrate addSmartspaceView from KeyguardClockSwitchController
@@ -135,15 +135,15 @@ constructor(
ConstraintSet.START,
ConstraintSet.PARENT_ID,
ConstraintSet.START,
- horizontalPaddingStart
+ horizontalPaddingStart,
)
connect(
sharedR.id.bc_smartspace_view,
ConstraintSet.END,
- if (keyguardClockViewModel.clockShouldBeCentered.value) ConstraintSet.PARENT_ID
- else R.id.split_shade_guideline,
+ if (keyguardSmartspaceViewModel.isShadeLayoutWide.value) R.id.split_shade_guideline
+ else ConstraintSet.PARENT_ID,
ConstraintSet.END,
- horizontalPaddingEnd
+ horizontalPaddingEnd,
)
if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) {
@@ -152,7 +152,7 @@ constructor(
sharedR.id.date_smartspace_view,
ConstraintSet.BOTTOM,
sharedR.id.bc_smartspace_view,
- ConstraintSet.TOP
+ ConstraintSet.TOP,
)
} else {
clear(sharedR.id.date_smartspace_view, ConstraintSet.BOTTOM)
@@ -160,13 +160,13 @@ constructor(
sharedR.id.date_smartspace_view,
ConstraintSet.TOP,
customR.id.lockscreen_clock_view,
- ConstraintSet.BOTTOM
+ ConstraintSet.BOTTOM,
)
connect(
sharedR.id.bc_smartspace_view,
ConstraintSet.TOP,
sharedR.id.date_smartspace_view,
- ConstraintSet.BOTTOM
+ ConstraintSet.BOTTOM,
)
}
@@ -174,10 +174,7 @@ constructor(
R.id.smart_space_barrier_bottom,
Barrier.BOTTOM,
0,
- *intArrayOf(
- sharedR.id.bc_smartspace_view,
- sharedR.id.date_smartspace_view,
- )
+ *intArrayOf(sharedR.id.bc_smartspace_view, sharedR.id.date_smartspace_view),
)
}
updateVisibility(constraintSet)
@@ -212,7 +209,7 @@ constructor(
setVisibility(sharedR.id.weather_smartspace_view, weatherVisibility)
setAlpha(
sharedR.id.weather_smartspace_view,
- if (weatherVisibility == View.VISIBLE) 1f else 0f
+ if (weatherVisibility == View.VISIBLE) 1f else 0f,
)
val dateVisibility =
if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) ConstraintSet.GONE
@@ -220,7 +217,7 @@ constructor(
setVisibility(sharedR.id.date_smartspace_view, dateVisibility)
setAlpha(
sharedR.id.date_smartspace_view,
- if (dateVisibility == ConstraintSet.VISIBLE) 1f else 0f
+ if (dateVisibility == ConstraintSet.VISIBLE) 1f else 0f,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
index 5065fcbbac93..19652525bee0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
@@ -31,8 +31,10 @@ import com.android.systemui.shade.ShadeDisplayAware
import javax.inject.Inject
import kotlin.math.roundToInt
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
@@ -40,6 +42,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
/** Models the UI state for the device entry icon foreground view (displayed icon). */
+@OptIn(FlowPreview::class)
@ExperimentalCoroutinesApi
@SysUISingleton
class DeviceEntryForegroundViewModel
@@ -97,7 +100,7 @@ constructor(
private val padding: Flow<Int> =
deviceEntryUdfpsInteractor.isUdfpsSupported.flatMapLatest { udfpsSupported ->
if (udfpsSupported) {
- udfpsOverlayInteractor.iconPadding
+ udfpsOverlayInteractor.iconPadding.debounce(udfpsPaddingDebounceDuration.toLong())
} else {
configurationInteractor.scaleForResolution.map { scale ->
(context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
@@ -120,6 +123,9 @@ constructor(
)
}
+ private val udfpsPaddingDebounceDuration: Int
+ get() = context.resources.getInteger(R.integer.udfps_padding_debounce_duration)
+
data class ForegroundIconViewModel(
val type: DeviceEntryIconView.IconType,
val useAodVariant: Boolean,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
index de0927ec27cb..3266dc45427a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
@@ -22,6 +22,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardSmartspaceInteractor
import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -39,6 +40,7 @@ constructor(
smartspaceController: LockscreenSmartspaceController,
keyguardClockViewModel: KeyguardClockViewModel,
smartspaceInteractor: KeyguardSmartspaceInteractor,
+ shadeInteractor: ShadeInteractor,
) {
/** Whether the smartspace section is available in the build. */
val isSmartspaceEnabled: Boolean = smartspaceController.isEnabled
@@ -89,6 +91,8 @@ constructor(
/* trigger clock and smartspace constraints change when smartspace appears */
val bcSmartspaceVisibility: StateFlow<Int> = smartspaceInteractor.bcSmartspaceVisibility
+ val isShadeLayoutWide: StateFlow<Boolean> = shadeInteractor.isShadeLayoutWide
+
companion object {
fun getSmartspaceStartMargin(context: Context): Int {
return context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_start) +
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/INoteTaskBubblesService.aidl b/packages/SystemUI/src/com/android/systemui/notetask/INoteTaskBubblesService.aidl
index 3e947d9d2b16..7803f229dda7 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/INoteTaskBubblesService.aidl
+++ b/packages/SystemUI/src/com/android/systemui/notetask/INoteTaskBubblesService.aidl
@@ -16,6 +16,7 @@
package com.android.systemui.notetask;
+import com.android.systemui.notetask.NoteTaskBubbleExpandBehavior;
import android.content.Intent;
import android.graphics.drawable.Icon;
import android.os.UserHandle;
@@ -25,5 +26,6 @@ interface INoteTaskBubblesService {
boolean areBubblesAvailable();
- void showOrHideAppBubble(in Intent intent, in UserHandle userHandle, in Icon icon);
+ void showOrHideAppBubble(in Intent intent, in UserHandle userHandle, in Icon icon,
+ in NoteTaskBubbleExpandBehavior bubbleExpandBehavior);
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubbleExpandBehavior.aidl b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubbleExpandBehavior.aidl
new file mode 100644
index 000000000000..86a06a568a4b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubbleExpandBehavior.aidl
@@ -0,0 +1,3 @@
+package com.android.systemui.notetask;
+
+parcelable NoteTaskBubbleExpandBehavior; \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubbleExpandBehavior.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubbleExpandBehavior.kt
new file mode 100644
index 000000000000..63b38a1b73f9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubbleExpandBehavior.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 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.notetask
+
+import android.os.Parcel
+import android.os.Parcelable
+
+enum class NoteTaskBubbleExpandBehavior : Parcelable {
+ /**
+ * The default bubble expand behavior for note task bubble: The bubble will collapse if there is
+ * already an expanded bubble, The bubble will expand if there is a collapsed bubble.
+ */
+ DEFAULT,
+ /**
+ * The special bubble expand behavior for note task bubble: The bubble will stay expanded, not
+ * collapse, if there is already an expanded bubble, The bubble will expand if there is a
+ * collapsed bubble.
+ */
+ KEEP_IF_EXPANDED;
+
+ override fun describeContents(): Int {
+ return 0
+ }
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeString(name)
+ }
+
+ companion object CREATOR : Parcelable.Creator<NoteTaskBubbleExpandBehavior> {
+ override fun createFromParcel(parcel: Parcel?): NoteTaskBubbleExpandBehavior {
+ return parcel?.readString()?.let { valueOf(it) } ?: DEFAULT
+ }
+
+ override fun newArray(size: Int) = arrayOfNulls<NoteTaskBubbleExpandBehavior>(size)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubblesController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubblesController.kt
index ec205f87d9fa..169285f6742e 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubblesController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubblesController.kt
@@ -27,6 +27,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.DebugLogger.debugLog
+import com.android.wm.shell.bubbles.Bubble
import com.android.wm.shell.bubbles.Bubbles
import java.util.Optional
import javax.inject.Inject
@@ -48,7 +49,7 @@ open class NoteTaskBubblesController
@Inject
constructor(
@Application private val context: Context,
- @Background private val bgDispatcher: CoroutineDispatcher
+ @Background private val bgDispatcher: CoroutineDispatcher,
) {
private val serviceConnector: ServiceConnector<INoteTaskBubblesService> =
@@ -57,7 +58,7 @@ constructor(
Intent(context, NoteTaskBubblesService::class.java),
Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE,
UserHandle.USER_SYSTEM,
- INoteTaskBubblesService.Stub::asInterface
+ INoteTaskBubblesService.Stub::asInterface,
)
/** Returns whether notes app bubble is supported. */
@@ -79,11 +80,12 @@ constructor(
open suspend fun showOrHideAppBubble(
intent: Intent,
userHandle: UserHandle,
- icon: Icon
+ icon: Icon,
+ bubbleExpandBehavior: NoteTaskBubbleExpandBehavior,
) {
withContext(bgDispatcher) {
serviceConnector
- .post { it.showOrHideAppBubble(intent, userHandle, icon) }
+ .post { it.showOrHideAppBubble(intent, userHandle, icon, bubbleExpandBehavior) }
.whenComplete { _, error ->
if (error != null) {
debugLog(error = error) {
@@ -120,16 +122,28 @@ constructor(
override fun showOrHideAppBubble(
intent: Intent,
userHandle: UserHandle,
- icon: Icon
+ icon: Icon,
+ bubbleExpandBehavior: NoteTaskBubbleExpandBehavior,
) {
mOptionalBubbles.ifPresentOrElse(
- { bubbles -> bubbles.showOrHideAppBubble(intent, userHandle, icon) },
+ { bubbles ->
+ if (
+ bubbleExpandBehavior ==
+ NoteTaskBubbleExpandBehavior.KEEP_IF_EXPANDED &&
+ bubbles.isBubbleExpanded(
+ Bubble.getAppBubbleKeyForApp(intent.`package`, userHandle)
+ )
+ ) {
+ return@ifPresentOrElse
+ }
+ bubbles.showOrHideAppBubble(intent, userHandle, icon)
+ },
{
debugLog {
"Failed to show or hide bubble for intent $intent," +
"user $user, and icon $icon as bubble is empty."
}
- }
+ },
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index 1fa5baaa21ae..a615963ed2ca 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -84,7 +84,7 @@ constructor(
private val userTracker: UserTracker,
private val secureSettings: SecureSettings,
@Application private val applicationScope: CoroutineScope,
- @Background private val bgCoroutineContext: CoroutineContext
+ @Background private val bgCoroutineContext: CoroutineContext,
) {
@VisibleForTesting val infoReference = AtomicReference<NoteTaskInfo?>()
@@ -98,7 +98,7 @@ constructor(
if (key != Bubble.getAppBubbleKeyForApp(info.packageName, info.user)) return
// Safe guard mechanism, this callback should only be called for app bubbles.
- if (info.launchMode != NoteTaskLaunchMode.AppBubble) return
+ if (info.launchMode !is NoteTaskLaunchMode.AppBubble) return
if (isExpanding) {
debugLog { "onBubbleExpandChanged - expanding: $info" }
@@ -117,10 +117,8 @@ constructor(
} else {
getUserForHandlingNotesTaking(entryPoint)
}
- activityContext.startActivityAsUser(
- createNotesRoleHolderSettingsIntent(),
- user
- )
+
+ activityContext.startActivityAsUser(createNotesRoleHolderSettingsIntent(), user)
}
/**
@@ -140,8 +138,7 @@ constructor(
entryPoint == QUICK_AFFORDANCE -> {
userTracker.userProfiles
.firstOrNull { userManager.isManagedProfile(it.id) }
- ?.userHandle
- ?: userTracker.userHandle
+ ?.userHandle ?: userTracker.userHandle
}
// On work profile devices, SysUI always run in the main user.
else -> userTracker.userHandle
@@ -158,19 +155,14 @@ constructor(
*
* That will let users open other apps in full screen, and take contextual notes.
*/
- fun showNoteTask(
- entryPoint: NoteTaskEntryPoint,
- ) {
+ fun showNoteTask(entryPoint: NoteTaskEntryPoint) {
if (!isEnabled) return
showNoteTaskAsUser(entryPoint, getUserForHandlingNotesTaking(entryPoint))
}
/** A variant of [showNoteTask] which launches note task in the given [user]. */
- fun showNoteTaskAsUser(
- entryPoint: NoteTaskEntryPoint,
- user: UserHandle,
- ) {
+ fun showNoteTaskAsUser(entryPoint: NoteTaskEntryPoint, user: UserHandle) {
if (!isEnabled) return
applicationScope.launch("$TAG#showNoteTaskAsUser") {
@@ -178,10 +170,7 @@ constructor(
}
}
- private suspend fun awaitShowNoteTaskAsUser(
- entryPoint: NoteTaskEntryPoint,
- user: UserHandle,
- ) {
+ private suspend fun awaitShowNoteTaskAsUser(entryPoint: NoteTaskEntryPoint, user: UserHandle) {
if (!isEnabled) return
if (!noteTaskBubblesController.areBubblesAvailable()) {
@@ -222,7 +211,13 @@ constructor(
val intent = createNoteTaskIntent(info)
val icon =
Icon.createWithResource(context, R.drawable.ic_note_task_shortcut_widget)
- noteTaskBubblesController.showOrHideAppBubble(intent, user, icon)
+ noteTaskBubblesController.showOrHideAppBubble(
+ intent,
+ user,
+ icon,
+ info.launchMode.bubbleExpandBehavior,
+ )
+
// App bubble logging happens on `onBubbleExpandChanged`.
debugLog { "onShowNoteTask - opened as app bubble: $info" }
}
@@ -399,8 +394,8 @@ constructor(
const val EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE = "extra_shortcut_badge_override_package"
/** Returns notes role holder settings intent. */
- fun createNotesRoleHolderSettingsIntent() = Intent(Intent.ACTION_MANAGE_DEFAULT_APP).
- putExtra(Intent.EXTRA_ROLE_NAME, ROLE_NOTES)
+ fun createNotesRoleHolderSettingsIntent() =
+ Intent(Intent.ACTION_MANAGE_DEFAULT_APP).putExtra(Intent.EXTRA_ROLE_NAME, ROLE_NOTES)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt
index 269eb870686c..8319e07525d8 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt
@@ -31,6 +31,10 @@ data class NoteTaskInfo(
if (isKeyguardLocked || entryPoint == WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE) {
NoteTaskLaunchMode.Activity
} else {
- NoteTaskLaunchMode.AppBubble
+ if (entryPoint == NoteTaskEntryPoint.QS_NOTES_TILE) {
+ NoteTaskLaunchMode.AppBubble(NoteTaskBubbleExpandBehavior.KEEP_IF_EXPANDED)
+ } else {
+ NoteTaskLaunchMode.AppBubble(NoteTaskBubbleExpandBehavior.DEFAULT)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskLaunchMode.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskLaunchMode.kt
index 836e103f4d69..6c85f20f2bce 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskLaunchMode.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskLaunchMode.kt
@@ -26,7 +26,8 @@ import com.android.wm.shell.bubbles.Bubbles
sealed class NoteTaskLaunchMode {
/** @see Bubbles.showOrHideAppBubble */
- object AppBubble : NoteTaskLaunchMode()
+ data class AppBubble(val bubbleExpandBehavior: NoteTaskBubbleExpandBehavior) :
+ NoteTaskLaunchMode()
/** @see Context.startActivity */
object Activity : NoteTaskLaunchMode()
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
index a1c5c9c682c3..5d5465633f9f 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
@@ -41,6 +41,7 @@ import com.android.systemui.qs.tiles.impl.notes.domain.model.NotesTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
+import com.android.systemui.qs.tiles.viewmodel.StubQSTileViewModel
import com.android.systemui.res.R
import dagger.Binds
import dagger.Module
@@ -106,12 +107,14 @@ interface NoteTaskModule {
stateInteractor: NotesTileDataInteractor,
userActionInteractor: NotesTileUserActionInteractor,
): QSTileViewModel =
- factory.create(
- TileSpec.create(NOTES_TILE_SPEC),
- userActionInteractor,
- stateInteractor,
- mapper,
- )
+ if (com.android.systemui.Flags.qsNewTilesFuture())
+ factory.create(
+ TileSpec.create(NOTES_TILE_SPEC),
+ userActionInteractor,
+ stateInteractor,
+ mapper,
+ )
+ else StubQSTileViewModel
@Provides
@IntoMap
@@ -120,10 +123,10 @@ interface NoteTaskModule {
QSTileConfig(
tileSpec = TileSpec.create(NOTES_TILE_SPEC),
uiConfig =
- QSTileUIConfig.Resource(
- iconRes = R.drawable.ic_qs_notes,
- labelRes = R.string.quick_settings_notes_label,
- ),
+ QSTileUIConfig.Resource(
+ iconRes = R.drawable.ic_qs_notes,
+ labelRes = R.string.quick_settings_notes_label,
+ ),
instanceId = uiEventLogger.getNewInstanceId(),
category = TileCategory.UTILITIES,
)
diff --git a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakeSleepReason.kt b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakeSleepReason.kt
index 776a8f47f056..c57b53bab442 100644
--- a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakeSleepReason.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakeSleepReason.kt
@@ -26,6 +26,9 @@ enum class WakeSleepReason(
/** The physical power button was pressed to wake up or sleep the device. */
POWER_BUTTON(isTouch = false, PowerManager.WAKE_REASON_POWER_BUTTON),
+ /** The sleep button was pressed to sleep the device. */
+ SLEEP_BUTTON(isTouch = false, PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON),
+
/** The user has tapped or double tapped to wake the screen. */
TAP(isTouch = true, PowerManager.WAKE_REASON_TAP),
@@ -78,6 +81,7 @@ enum class WakeSleepReason(
fun fromPowerManagerSleepReason(reason: Int): WakeSleepReason {
return when (reason) {
PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON -> POWER_BUTTON
+ PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON -> SLEEP_BUTTON
PowerManager.GO_TO_SLEEP_REASON_TIMEOUT -> TIMEOUT
PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD -> FOLD
else -> OTHER
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
index 8b0694219630..0d464f5a0936 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
@@ -28,6 +28,7 @@ import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository.Compa
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository
import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.shade.ShadeDisplayAware
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
@@ -48,7 +49,7 @@ class QSHostAdapter
@Inject
constructor(
private val interactor: CurrentTilesInteractor,
- private val context: Context,
+ @ShadeDisplayAware private val context: Context,
private val tileServiceRequestControllerBuilder: TileServiceRequestController.Builder,
@Application private val scope: CoroutineScope,
dumpManager: DumpManager,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 85bcc25e140c..afb852ae824c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -50,6 +50,7 @@ import com.android.systemui.util.kotlin.JavaAdapterKt;
import kotlin.Unit;
import kotlin.jvm.functions.Function1;
+import kotlinx.coroutines.DisposableHandle;
import kotlinx.coroutines.flow.StateFlow;
import java.io.PrintWriter;
@@ -107,6 +108,8 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
setLayoutForMediaInScene();
};
+ private DisposableHandle mJavaAdapterDisposableHandle;
+
private boolean mLastListening;
@VisibleForTesting
@@ -221,6 +224,9 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
mView.removeTile(record);
}
mRecords.clear();
+ if (mJavaAdapterDisposableHandle != null) {
+ mJavaAdapterDisposableHandle.dispose();
+ }
}
@Override
@@ -255,7 +261,7 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
}
private void registerForMediaInteractorChanges() {
- JavaAdapterKt.collectFlow(
+ mJavaAdapterDisposableHandle = JavaAdapterKt.collectFlow(
mView,
getMediaVisibleFlow(),
mMediaOrRecommendationVisibleConsumer
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
index d38f8492c883..88f0318c2fbf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
@@ -87,6 +87,7 @@ import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig;
import com.android.systemui.res.R;
import com.android.systemui.security.data.model.SecurityModel;
import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shade.ShadeDisplayAware;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.SecurityController;
@@ -177,7 +178,7 @@ public class QSSecurityFooterUtils implements DialogInterface.OnClickListener {
@Inject
QSSecurityFooterUtils(
- @Application Context context, DevicePolicyManager devicePolicyManager,
+ @ShadeDisplayAware Context context, DevicePolicyManager devicePolicyManager,
UserTracker userTracker, @Main Handler mainHandler, ActivityStarter activityStarter,
SecurityController securityController, @Background Looper bgLooper,
DialogTransitionAnimator dialogTransitionAnimator) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModule.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModule.kt
index 676f6a426264..2ec729223a8d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModule.kt
@@ -20,6 +20,7 @@ import android.content.Context
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.qs.flags.QSComposeFragment
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.util.Utils
import dagger.Module
import dagger.Provides
@@ -34,7 +35,7 @@ interface QSFragmentComposeModule {
@Provides
@SysUISingleton
@Named(QS_USING_MEDIA_PLAYER)
- fun providesUsingMedia(@Application context: Context): Boolean {
+ fun providesUsingMedia(@ShadeDisplayAware context: Context): Boolean {
return QSComposeFragment.isEnabled && Utils.useQsMediaPlayer(context)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index 89f85ab14dd6..15e34990e111 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -43,6 +43,7 @@ import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon;
import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shade.ShadeDisplayAware;
import java.util.ArrayList;
import java.util.Arrays;
@@ -69,7 +70,7 @@ public class TileQueryHelper {
@Inject
public TileQueryHelper(
- Context context,
+ @ShadeDisplayAware Context context,
UserTracker userTracker,
@Main Executor mainExecutor,
@Background Executor bgExecutor
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
index 564bc78a3f98..8ef637545e69 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
@@ -37,6 +37,7 @@ import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel
import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractor
import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig
import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.util.icuMessageFormat
import javax.inject.Inject
import javax.inject.Named
@@ -112,7 +113,7 @@ class FooterActionsViewModel(
class Factory
@Inject
constructor(
- @Application private val context: Context,
+ @ShadeDisplayAware private val context: Context,
private val falsingManager: FalsingManager,
private val footerActionsInteractor: FooterActionsInteractor,
private val globalActionsDialogLiteProvider: Provider<GlobalActionsDialogLite>,
@@ -175,7 +176,7 @@ class FooterActionsViewModel(
}
fun FooterActionsViewModel(
- @Application appContext: Context,
+ @ShadeDisplayAware appContext: Context,
footerActionsInteractor: FooterActionsInteractor,
falsingManager: FalsingManager,
globalActionsDialogLite: GlobalActionsDialogLite,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
index 4e094cc77eae..789fdebc36eb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
@@ -16,12 +16,17 @@
package com.android.systemui.qs.panels.ui.compose
-import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Arrangement.spacedBy
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.requiredHeight
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.pager.HorizontalPager
+import androidx.compose.foundation.pager.PagerState
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
@@ -39,16 +44,17 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.padding
import com.android.systemui.compose.modifiers.sysuiResTag
+import com.android.systemui.development.ui.compose.BuildNumber
+import com.android.systemui.development.ui.viewmodel.BuildNumberViewModel
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.qs.panels.dagger.PaginatedBaseLayoutType
-import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout.Dimensions.FooterHeight
-import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout.Dimensions.InterPageSpacing
+import com.android.systemui.qs.panels.ui.compose.Dimensions.FooterHeight
+import com.android.systemui.qs.panels.ui.compose.Dimensions.InterPageSpacing
import com.android.systemui.qs.panels.ui.viewmodel.PaginatedGridViewModel
import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
import com.android.systemui.qs.ui.compose.borderOnFocus
@@ -121,38 +127,78 @@ constructor(
TileGrid(tiles = page, modifier = Modifier, editModeStart = {})
}
}
- // Use requiredHeight so it won't be squished if the view doesn't quite fit. As this is
- // expected to be inside a scrollable container, this should not be an issue.
- Box(modifier = Modifier.requiredHeight(FooterHeight).fillMaxWidth()) {
- PagerDots(
- pagerState = pagerState,
- activeColor = MaterialTheme.colorScheme.primary,
- nonActiveColor = MaterialTheme.colorScheme.surfaceVariant,
- modifier = Modifier.align(Alignment.Center),
- )
- CompositionLocalProvider(value = LocalContentColor provides Color.White) {
- IconButton(
- onClick = editModeStart,
- shape = RoundedCornerShape(CornerSize(28.dp)),
- modifier =
- Modifier.align(Alignment.CenterEnd)
- .borderOnFocus(
- color = MaterialTheme.colorScheme.secondary,
- cornerSize = CornerSize(FooterHeight / 2),
- ),
- ) {
- Icon(
- imageVector = Icons.Default.Edit,
- contentDescription = stringResource(id = R.string.qs_edit),
+ FooterBar(
+ buildNumberViewModelFactory = viewModel.buildNumberViewModelFactory,
+ pagerState = pagerState,
+ editModeStart = editModeStart,
+ )
+ }
+ }
+}
+
+private object Dimensions {
+ val FooterHeight = 48.dp
+ val InterPageSpacing = 16.dp
+}
+
+@Composable
+private fun FooterBar(
+ buildNumberViewModelFactory: BuildNumberViewModel.Factory,
+ pagerState: PagerState,
+ editModeStart: () -> Unit,
+) {
+ // Use requiredHeight so it won't be squished if the view doesn't quite fit. As this is
+ // expected to be inside a scrollable container, this should not be an issue.
+ // Also, we construct the layout this way to do the following:
+ // * PagerDots is centered in the row, taking as much space as it needs.
+ // * On the start side, we place the BuildNumber, taking as much space as it needs, but
+ // constrained by the available space left over after PagerDots
+ // * On the end side, we place the edit mode button, with the same constraints as for
+ // BuildNumber (but it will usually fit, as it's just a square button).
+ Row(
+ modifier = Modifier.requiredHeight(FooterHeight).fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = spacedBy(8.dp),
+ ) {
+ Row(Modifier.weight(1f)) {
+ BuildNumber(
+ viewModelFactory = buildNumberViewModelFactory,
+ textColor = MaterialTheme.colorScheme.onSurface,
+ modifier =
+ Modifier.borderOnFocus(
+ color = MaterialTheme.colorScheme.secondary,
+ cornerSize = CornerSize(1.dp),
)
- }
+ .wrapContentSize(),
+ )
+ Spacer(modifier = Modifier.weight(1f))
+ }
+ PagerDots(
+ pagerState = pagerState,
+ activeColor = MaterialTheme.colorScheme.primary,
+ nonActiveColor = MaterialTheme.colorScheme.surfaceVariant,
+ modifier = Modifier.wrapContentWidth(),
+ )
+ Row(Modifier.weight(1f)) {
+ Spacer(modifier = Modifier.weight(1f))
+ CompositionLocalProvider(
+ value = LocalContentColor provides MaterialTheme.colorScheme.onSurface
+ ) {
+ IconButton(
+ onClick = editModeStart,
+ shape = RoundedCornerShape(CornerSize(28.dp)),
+ modifier =
+ Modifier.borderOnFocus(
+ color = MaterialTheme.colorScheme.secondary,
+ cornerSize = CornerSize(FooterHeight / 2),
+ ),
+ ) {
+ Icon(
+ imageVector = Icons.Default.Edit,
+ contentDescription = stringResource(id = R.string.qs_edit),
+ )
}
}
}
}
-
- private object Dimensions {
- val FooterHeight = 48.dp
- val InterPageSpacing = 16.dp
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
index cb57c6710553..0a80a19871fd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
@@ -126,7 +126,7 @@ fun Tile(
val currentBounceableInfo by rememberUpdatedState(bounceableInfo)
val resources = resources()
val uiState = remember(state, resources) { state.toUiState(resources) }
- val colors = TileDefaults.getColorForState(uiState)
+ val colors = TileDefaults.getColorForState(uiState, iconOnly)
val hapticsViewModel: TileHapticsViewModel? =
rememberViewModel(traceName = "TileHapticsViewModel") {
tileHapticsViewModelFactoryProvider.getHapticsViewModelFactory()?.create(tile)
@@ -365,22 +365,24 @@ private object TileDefaults {
)
@Composable
- fun getColorForState(uiState: TileUiState): TileColors {
+ fun getColorForState(uiState: TileUiState, iconOnly: Boolean): TileColors {
return when (uiState.state) {
STATE_ACTIVE -> {
- if (uiState.handlesSecondaryClick) {
+ if (uiState.handlesSecondaryClick && !iconOnly) {
activeDualTargetTileColors()
} else {
activeTileColors()
}
}
+
STATE_INACTIVE -> {
- if (uiState.handlesSecondaryClick) {
+ if (uiState.handlesSecondaryClick && !iconOnly) {
inactiveDualTargetTileColors()
} else {
inactiveTileColors()
}
}
+
else -> unavailableTileColors()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
index 4e34e73654fc..faab6960a99c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
@@ -56,7 +56,7 @@ constructor(
private val tilesAvailabilityInteractor: TilesAvailabilityInteractor,
private val minTilesInteractor: MinimumTilesInteractor,
@ShadeDisplayAware private val configurationInteractor: ConfigurationInteractor,
- @Application private val applicationContext: Context,
+ @ShadeDisplayAware private val context: Context,
@Named("Default") private val defaultGridLayout: GridLayout,
@Application private val applicationScope: CoroutineScope,
gridLayoutTypeInteractor: GridLayoutTypeInteractor,
@@ -140,7 +140,7 @@ constructor(
.combine(configurationInteractor.onAnyConfigurationChange.emitOnStart()) {
tiles,
_ ->
- tiles.fastMap { it.load(applicationContext) }
+ tiles.fastMap { it.load(context) }
}
} else {
emptyFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt
index e5607eb6e620..bff330b98fda 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.panels.ui.viewmodel
import androidx.compose.runtime.getValue
+import com.android.systemui.development.ui.viewmodel.BuildNumberViewModel
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QS
@@ -34,6 +35,7 @@ constructor(
columnsWithMediaViewModelFactory: QSColumnsViewModel.Factory,
paginatedGridInteractor: PaginatedGridInteractor,
inFirstPageViewModel: InFirstPageViewModel,
+ val buildNumberViewModelFactory: BuildNumberViewModel.Factory,
) : IconTilesViewModel by iconTilesViewModel, ExclusiveActivatable() {
private val hydrator = Hydrator("PaginatedGridViewModel")
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
index c5b27376a82a..41cdefdb2396 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
@@ -30,8 +30,8 @@ import androidx.annotation.GuardedBy
import com.android.systemui.common.data.repository.PackageChangeRepository
import com.android.systemui.common.shared.model.PackageChangeModel
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.util.kotlin.isComponentActuallyEnabled
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -54,7 +54,7 @@ interface InstalledTilesComponentRepository {
class InstalledTilesComponentRepositoryImpl
@Inject
constructor(
- @Application private val applicationContext: Context,
+ @ShadeDisplayAware private val context: Context,
@Background private val backgroundScope: CoroutineScope,
private val packageChangeRepository: PackageChangeRepository
) : InstalledTilesComponentRepository {
@@ -77,10 +77,10 @@ constructor(
* context.
*/
val packageManager =
- if (applicationContext.userId == userId) {
- applicationContext.packageManager
+ if (context.userId == userId) {
+ context.packageManager
} else {
- applicationContext
+ context
.createContextAsUser(
UserHandle.of(userId),
/* flags */ 0,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddable.kt
index 31ea734fb842..e9c91ca0db12 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddable.kt
@@ -27,6 +27,7 @@ import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
import com.android.systemui.qs.pipeline.domain.model.AutoAddable
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.NightDisplayTile
+import com.android.systemui.shade.ShadeDisplayAware
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
@@ -42,7 +43,7 @@ class NightDisplayAutoAddable
@Inject
constructor(
private val nightDisplayListenerBuilder: NightDisplayListenerModule.Builder,
- context: Context,
+ @ShadeDisplayAware context: Context,
) : AutoAddable {
private val enabled = ColorDisplayManager.isNightDisplayAvailable(context)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
index 6b654beea149..fed8b60a653f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -39,6 +39,7 @@ import com.android.systemui.qs.PseudoGridView;
import com.android.systemui.qs.QSUserSwitcherEvent;
import com.android.systemui.qs.user.UserSwitchDialogController;
import com.android.systemui.res.R;
+import com.android.systemui.shade.ShadeDisplayAware;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter;
import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -95,7 +96,7 @@ public class UserDetailView extends PseudoGridView {
}
@Inject
- public Adapter(Context context, UserSwitcherController controller,
+ public Adapter(@ShadeDisplayAware Context context, UserSwitcherController controller,
UiEventLogger uiEventLogger, FalsingManager falsingManager) {
super(controller);
mContext = context;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt
index 87b89ea6810a..45775272e01f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt
@@ -27,6 +27,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor.PolicyResult
+import com.android.systemui.shade.ShadeDisplayAware
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
@@ -70,7 +71,7 @@ interface DisabledByPolicyInteractor {
class DisabledByPolicyInteractorImpl
@Inject
constructor(
- private val context: Context,
+ @ShadeDisplayAware private val context: Context,
private val activityStarter: ActivityStarter,
private val restrictedLockProxy: RestrictedLockProxy,
@Background private val backgroundDispatcher: CoroutineDispatcher,
@@ -105,7 +106,7 @@ constructor(
/** Mockable proxy for [RestrictedLockUtilsInternal] static methods. */
@VisibleForTesting
-class RestrictedLockProxy @Inject constructor(private val context: Context) {
+class RestrictedLockProxy @Inject constructor(@ShadeDisplayAware private val context: Context) {
@WorkerThread
fun hasBaseUserRestriction(userId: Int, userRestriction: String?): Boolean =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt
index b766ee0d4333..222fa3efbe94 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt
@@ -28,6 +28,7 @@ import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider
import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProviderImpl
import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
+import com.android.systemui.shade.ShadeDisplayAware
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -66,6 +67,6 @@ interface QSTilesModule {
companion object {
- @Provides fun provideTilesTheme(context: Context): Theme = context.theme
+ @Provides fun provideTilesTheme(@ShadeDisplayAware context: Context): Theme = context.theme
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 8f6c4e743269..244f024625db 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -87,6 +87,7 @@ import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.res.R;
+import com.android.systemui.shade.ShadeDisplayAware;
import com.android.systemui.statusbar.connectivity.AccessPointController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.LocationController;
@@ -240,7 +241,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi
}
@Inject
- public InternetDialogController(@NonNull Context context, UiEventLogger uiEventLogger,
+ public InternetDialogController(@ShadeDisplayAware Context context, UiEventLogger uiEventLogger,
ActivityStarter starter, AccessPointController accessPointController,
SubscriptionManager subscriptionManager, TelephonyManager telephonyManager,
@Nullable WifiManager wifiManager, ConnectivityManager connectivityManager,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
index 89b9eee52f2a..0ab533bb9838 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
@@ -71,6 +71,7 @@ import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.res.R;
+import com.android.systemui.shade.ShadeDisplayAware;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.wifitrackerlib.WifiEntry;
@@ -190,7 +191,7 @@ public class InternetDialogDelegate implements
@AssistedInject
public InternetDialogDelegate(
- Context context,
+ @ShadeDisplayAware Context context,
InternetDialogManager internetDialogManager,
InternetDialogController internetDialogController,
@Assisted(CAN_CONFIG_MOBILE_DATA) boolean canConfigMobileData,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileDefaultsRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileDefaultsRepository.kt
index 1546ec2c54bd..32fb1d18724d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileDefaultsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileDefaultsRepository.kt
@@ -26,6 +26,7 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
import com.android.systemui.qs.tiles.impl.di.QSTileScope
+import com.android.systemui.shade.ShadeDisplayAware
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -68,7 +69,7 @@ interface CustomTileDefaultsRepository {
class CustomTileDefaultsRepositoryImpl
@Inject
constructor(
- private val context: Context,
+ @ShadeDisplayAware private val context: Context,
@Application applicationScope: CoroutineScope,
@Background private val backgroundDispatcher: CoroutineDispatcher,
) : CustomTileDefaultsRepository {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTilePackageUpdatesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTilePackageUpdatesRepository.kt
index 0ebd6f2b4ac3..cd4938f01b63 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTilePackageUpdatesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTilePackageUpdatesRepository.kt
@@ -28,6 +28,7 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.impl.di.QSTileScope
+import com.android.systemui.shade.ShadeDisplayAware
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
@@ -51,7 +52,7 @@ class CustomTilePackageUpdatesRepositoryImpl
@Inject
constructor(
private val tileSpec: TileSpec.CustomTileSpec,
- @Application private val context: Context,
+ @ShadeDisplayAware private val context: Context,
@QSTileScope private val tileScope: CoroutineScope,
@Background private val backgroundCoroutineContext: CoroutineContext,
) : CustomTilePackageUpdatesRepository {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt
index 60aa4ea4759f..c446865f31af 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt
@@ -30,12 +30,16 @@ import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.shade.ShadeDisplayAware
import javax.inject.Inject
@SysUISingleton
class CustomTileMapper
@Inject
-constructor(private val context: Context, private val uriGrantsManager: IUriGrantsManager) :
+constructor(
+ @ShadeDisplayAware private val context: Context,
+ private val uriGrantsManager: IUriGrantsManager
+) :
QSTileDataToStateMapper<CustomTileDataModel> {
override fun map(config: QSTileConfig, data: CustomTileDataModel): QSTileState {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt
index af2bb9d0d2f7..1153b5c67261 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt
@@ -42,6 +42,7 @@ import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataMod
import com.android.systemui.qs.tiles.impl.di.QSTileScope
import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
import com.android.systemui.settings.DisplayTracker
+import com.android.systemui.shade.ShadeDisplayAware
import java.util.concurrent.atomic.AtomicReference
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
@@ -51,7 +52,7 @@ import kotlinx.coroutines.withContext
class CustomTileUserActionInteractor
@Inject
constructor(
- private val context: Context,
+ @ShadeDisplayAware private val context: Context,
private val tileSpec: TileSpec,
private val qsTileLogger: QSTileLogger,
private val windowManager: IWindowManager,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
index fc945851cdad..1a6876d0b765 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
@@ -30,6 +30,7 @@ import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileMode
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.pipeline.shared.ui.model.InternetTileIconModel
import javax.inject.Inject
@@ -37,9 +38,9 @@ import javax.inject.Inject
class InternetTileMapper
@Inject
constructor(
- @Main private val resources: Resources,
+ @ShadeDisplayAware private val resources: Resources,
private val theme: Resources.Theme,
- private val context: Context,
+ @ShadeDisplayAware private val context: Context,
@Main private val handler: Handler,
) : QSTileDataToStateMapper<InternetTileModel> {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt
index 6fe3979fa446..6d10843decc0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt
@@ -28,6 +28,7 @@ import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
@@ -54,7 +55,7 @@ import kotlinx.coroutines.flow.stateIn
class InternetTileDataInteractor
@Inject
constructor(
- private val context: Context,
+ @ShadeDisplayAware private val context: Context,
@Application private val scope: CoroutineScope,
airplaneModeRepository: AirplaneModeRepository,
private val connectivityRepository: ConnectivityRepository,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
index 3e44258229f9..9b2880b6d47f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
@@ -28,6 +28,7 @@ import com.android.systemui.qs.tiles.ModesTile
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
import com.android.systemui.statusbar.policy.domain.model.ActiveZenModes
import com.android.systemui.statusbar.policy.domain.model.ZenModeInfo
@@ -42,7 +43,7 @@ import kotlinx.coroutines.flow.map
class ModesTileDataInteractor
@Inject
constructor(
- val context: Context,
+ @ShadeDisplayAware val context: Context,
val zenModeInteractor: ZenModeInteractor,
@Background val bgDispatcher: CoroutineDispatcher,
) : QSTileDataInteractor<ModesTileModel> {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractor.kt
index 252e3f84df6c..05bdf0a92679 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractor.kt
@@ -32,6 +32,7 @@ import com.android.systemui.qs.tiles.impl.saver.domain.DataSaverDialogDelegate
import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
import com.android.systemui.settings.UserFileManager
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.policy.DataSaverController
import javax.inject.Inject
@@ -42,7 +43,7 @@ import kotlinx.coroutines.withContext
class DataSaverTileUserActionInteractor
@Inject
constructor(
- @Application private val context: Context,
+ @ShadeDisplayAware private val context: Context,
@Main private val coroutineContext: CoroutineContext,
@Background private val backgroundContext: CoroutineContext,
private val dataSaverController: DataSaverController,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileDataInteractor.kt
index c928e8af17fc..7af3576d8cd9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileDataInteractor.kt
@@ -25,6 +25,7 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
import com.android.systemui.qs.tiles.impl.uimodenight.domain.model.UiModeNightTileModel
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.LocationController
@@ -38,7 +39,7 @@ import kotlinx.coroutines.flow.flowOf
class UiModeNightTileDataInteractor
@Inject
constructor(
- @Application private val context: Context,
+ @ShadeDisplayAware private val context: Context,
private val configurationController: ConfigurationController,
private val uiModeManager: UiModeManager,
private val batteryController: BatteryController,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
index 35b1b9636263..ab3862b75ee8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
@@ -81,7 +81,7 @@ constructor(
// additional
// guidance on how to auto add your tile
throw UnsupportedOperationException(
- "Turning on tile is not supported now"
+ "Turning on tile is not supported now. Tile spec: $tileSpec"
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 9125d7e8bb0e..066fe1b0d896 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -430,8 +430,13 @@ constructor(
"mechanism: ${deviceUnlockStatus.deviceUnlockSource}"
else -> null
}
- // Not on lockscreen or bouncer, so remain in the current scene.
- else -> null
+ // Not on lockscreen or bouncer, so remain in the current scene but since
+ // unlocked, replace the Lockscreen scene from the bottom of the navigation
+ // back stack with the Gone scene.
+ else -> {
+ replaceLockscreenSceneOnBackStack()
+ null
+ }
}
}
.collect { (targetSceneKey, loggingReason) ->
@@ -440,17 +445,19 @@ constructor(
}
}
- /** If the [Scenes.Lockscreen] is on the backstack, replaces it with [Scenes.Gone]. */
+ /**
+ * If the [Scenes.Lockscreen] is on the bottom of the navigation backstack, replaces it with
+ * [Scenes.Gone].
+ */
private fun replaceLockscreenSceneOnBackStack() {
sceneBackInteractor.updateBackStack { stack ->
val list = stack.asIterable().toMutableList()
- check(list.last() == Scenes.Lockscreen) {
- "The bottommost/last SceneKey of the back stack isn't" +
- " the Lockscreen scene like expected. The back" +
- " stack is $stack."
+ if (list.lastOrNull() == Scenes.Lockscreen) {
+ list[list.size - 1] = Scenes.Gone
+ sceneStackOf(*list.toTypedArray())
+ } else {
+ stack
}
- list[list.size - 1] = Scenes.Gone
- sceneStackOf(*list.toTypedArray())
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
index 2dcb706234b8..d0c033bb10b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
@@ -834,6 +834,15 @@ constructor(
public?.let {
it.layoutInflaterFactory = provider.provide(row, FLAG_CONTENT_VIEW_PUBLIC)
}
+ if (android.app.Flags.notificationsRedesignAppIcons()) {
+ normalGroupHeader?.let {
+ it.layoutInflaterFactory = provider.provide(row, FLAG_GROUP_SUMMARY_HEADER)
+ }
+ minimizedGroupHeader?.let {
+ it.layoutInflaterFactory =
+ provider.provide(row, FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER)
+ }
+ }
return this
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
index 7ef1e416aa19..5837a498e44f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
@@ -31,6 +31,7 @@ import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
import javax.inject.Inject;
@@ -63,17 +64,21 @@ public class ManagedProfileControllerImpl implements ManagedProfileController {
@Override
public void addCallback(@NonNull Callback callback) {
- mCallbacks.add(callback);
- if (mCallbacks.size() == 1) {
- setListening(true);
+ synchronized (mCallbacks) {
+ mCallbacks.add(callback);
+ if (mCallbacks.size() == 1) {
+ setListening(true);
+ }
+ callback.onManagedProfileChanged();
}
- callback.onManagedProfileChanged();
}
@Override
public void removeCallback(@NonNull Callback callback) {
- if (mCallbacks.remove(callback) && mCallbacks.size() == 0) {
- setListening(false);
+ synchronized (mCallbacks) {
+ if (mCallbacks.remove(callback) && mCallbacks.size() == 0) {
+ setListening(false);
+ }
}
}
@@ -109,10 +114,7 @@ public class ManagedProfileControllerImpl implements ManagedProfileController {
}
private void notifyManagedProfileRemoved() {
- ArrayList<Callback> copy = new ArrayList<>(mCallbacks);
- for (Callback callback : copy) {
- callback.onManagedProfileRemoved();
- }
+ notifyCallbacks(Callback::onManagedProfileRemoved);
}
public boolean hasActiveProfile() {
@@ -136,6 +138,16 @@ public class ManagedProfileControllerImpl implements ManagedProfileController {
}
}
+ private void notifyCallbacks(Consumer<Callback> method) {
+ ArrayList<Callback> copy;
+ synchronized (mCallbacks) {
+ copy = new ArrayList<>(mCallbacks);
+ }
+ for (Callback callback : copy) {
+ method.accept(callback);
+ }
+ }
+
private void setListening(boolean listening) {
if (mListening == listening) {
return;
@@ -154,19 +166,13 @@ public class ManagedProfileControllerImpl implements ManagedProfileController {
@Override
public void onUserChanged(int newUser, @NonNull Context userContext) {
reloadManagedProfiles();
- ArrayList<Callback> copy = new ArrayList<>(mCallbacks);
- for (Callback callback : copy) {
- callback.onManagedProfileChanged();
- }
+ notifyCallbacks(Callback::onManagedProfileChanged);
}
@Override
public void onProfilesChanged(@NonNull List<UserInfo> profiles) {
reloadManagedProfiles();
- ArrayList<Callback> copy = new ArrayList<>(mCallbacks);
- for (Callback callback : copy) {
- callback.onManagedProfileChanged();
- }
+ notifyCallbacks(Callback::onManagedProfileChanged);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
index d371acf86a28..66a900bd72d8 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
@@ -18,6 +18,7 @@ package com.android.systemui.touchpad.tutorial.ui.composable
import android.content.res.Configuration
import androidx.compose.foundation.background
+import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -36,8 +37,12 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.input.pointer.pointerInteropFilter
@@ -49,6 +54,7 @@ import com.android.systemui.inputdevice.tutorial.ui.composable.DoneButton
import com.android.systemui.res.R
import com.android.systemui.touchpad.tutorial.ui.gesture.isFourFingerTouchpadSwipe
import com.android.systemui.touchpad.tutorial.ui.gesture.isThreeFingerTouchpadSwipe
+import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen
@Composable
fun TutorialSelectionScreen(
@@ -56,6 +62,7 @@ fun TutorialSelectionScreen(
onHomeTutorialClicked: () -> Unit,
onRecentAppsTutorialClicked: () -> Unit,
onDoneButtonClicked: () -> Unit,
+ lastSelectedScreen: Screen,
) {
Column(
verticalArrangement = Arrangement.Center,
@@ -80,6 +87,7 @@ fun TutorialSelectionScreen(
onHomeTutorialClicked = onHomeTutorialClicked,
onRecentAppsTutorialClicked = onRecentAppsTutorialClicked,
modifier = Modifier.weight(1f).padding(60.dp),
+ lastSelectedScreen,
)
}
else -> {
@@ -88,6 +96,7 @@ fun TutorialSelectionScreen(
onHomeTutorialClicked = onHomeTutorialClicked,
onRecentAppsTutorialClicked = onRecentAppsTutorialClicked,
modifier = Modifier.weight(1f).padding(60.dp),
+ lastSelectedScreen,
)
}
}
@@ -105,6 +114,7 @@ private fun HorizontalSelectionButtons(
onHomeTutorialClicked: () -> Unit,
onRecentAppsTutorialClicked: () -> Unit,
modifier: Modifier = Modifier,
+ lastSelectedScreen: Screen,
) {
Row(
horizontalArrangement = Arrangement.spacedBy(20.dp),
@@ -116,6 +126,7 @@ private fun HorizontalSelectionButtons(
onHomeTutorialClicked,
onRecentAppsTutorialClicked,
modifier = Modifier.weight(1f).fillMaxSize(),
+ lastSelectedScreen,
)
}
}
@@ -126,6 +137,7 @@ private fun VerticalSelectionButtons(
onHomeTutorialClicked: () -> Unit,
onRecentAppsTutorialClicked: () -> Unit,
modifier: Modifier = Modifier,
+ lastSelectedScreen: Screen,
) {
Column(
verticalArrangement = Arrangement.spacedBy(20.dp),
@@ -137,6 +149,7 @@ private fun VerticalSelectionButtons(
onHomeTutorialClicked,
onRecentAppsTutorialClicked,
modifier = Modifier.weight(1f).fillMaxSize(),
+ lastSelectedScreen,
)
}
}
@@ -147,14 +160,26 @@ private fun ThreeTutorialButtons(
onHomeTutorialClicked: () -> Unit,
onRecentAppsTutorialClicked: () -> Unit,
modifier: Modifier = Modifier,
+ lastSelectedScreen: Screen,
) {
+ val homeFocusRequester = remember { FocusRequester() }
+ val backFocusRequester = remember { FocusRequester() }
+ val recentAppsFocusRequester = remember { FocusRequester() }
+ LaunchedEffect(Unit) {
+ when (lastSelectedScreen) {
+ Screen.HOME_GESTURE -> homeFocusRequester.requestFocus()
+ Screen.BACK_GESTURE -> backFocusRequester.requestFocus()
+ Screen.RECENT_APPS_GESTURE -> recentAppsFocusRequester.requestFocus()
+ else -> {} // No-Op.
+ }
+ }
TutorialButton(
text = stringResource(R.string.touchpad_tutorial_home_gesture_button),
icon = ImageVector.vectorResource(id = R.drawable.touchpad_tutorial_home_icon),
iconColor = MaterialTheme.colorScheme.onPrimary,
onClick = onHomeTutorialClicked,
backgroundColor = MaterialTheme.colorScheme.primary,
- modifier = modifier,
+ modifier = modifier.focusRequester(homeFocusRequester).focusable(),
)
TutorialButton(
text = stringResource(R.string.touchpad_tutorial_back_gesture_button),
@@ -162,7 +187,7 @@ private fun ThreeTutorialButtons(
iconColor = MaterialTheme.colorScheme.onTertiary,
onClick = onBackTutorialClicked,
backgroundColor = MaterialTheme.colorScheme.tertiary,
- modifier = modifier,
+ modifier = modifier.focusRequester(backFocusRequester).focusable(),
)
TutorialButton(
text = stringResource(R.string.touchpad_tutorial_recent_apps_gesture_button),
@@ -170,7 +195,7 @@ private fun ThreeTutorialButtons(
iconColor = MaterialTheme.colorScheme.onSecondary,
onClick = onRecentAppsTutorialClicked,
backgroundColor = MaterialTheme.colorScheme.secondary,
- modifier = modifier,
+ modifier = modifier.focusRequester(recentAppsFocusRequester).focusable(),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
index e1f7bd59005c..6662fc5c127c 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
@@ -24,12 +24,16 @@ import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.lifecycle.Lifecycle.State.STARTED
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.theme.PlatformTheme
import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger
import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger.TutorialContext
import com.android.systemui.inputdevice.tutorial.KeyboardTouchpadTutorialMetricsLogger
+import com.android.systemui.res.R
import com.android.systemui.touchpad.tutorial.ui.composable.BackGestureTutorialScreen
import com.android.systemui.touchpad.tutorial.ui.composable.HomeGestureTutorialScreen
import com.android.systemui.touchpad.tutorial.ui.composable.RecentAppsGestureTutorialScreen
@@ -54,6 +58,7 @@ constructor(
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
+ setTitle(getString(R.string.launch_touchpad_tutorial_notification_content))
setContent {
PlatformTheme { TouchpadTutorialScreen(vm, closeTutorial = ::finishTutorial) }
}
@@ -82,13 +87,24 @@ constructor(
@Composable
fun TouchpadTutorialScreen(vm: TouchpadTutorialViewModel, closeTutorial: () -> Unit) {
val activeScreen by vm.screen.collectAsStateWithLifecycle(STARTED)
+ var lastSelectedScreen by remember { mutableStateOf(TUTORIAL_SELECTION) }
when (activeScreen) {
TUTORIAL_SELECTION ->
TutorialSelectionScreen(
- onBackTutorialClicked = { vm.goTo(BACK_GESTURE) },
- onHomeTutorialClicked = { vm.goTo(HOME_GESTURE) },
- onRecentAppsTutorialClicked = { vm.goTo(RECENT_APPS_GESTURE) },
+ onBackTutorialClicked = {
+ lastSelectedScreen = BACK_GESTURE
+ vm.goTo(BACK_GESTURE)
+ },
+ onHomeTutorialClicked = {
+ lastSelectedScreen = HOME_GESTURE
+ vm.goTo(HOME_GESTURE)
+ },
+ onRecentAppsTutorialClicked = {
+ lastSelectedScreen = RECENT_APPS_GESTURE
+ vm.goTo(RECENT_APPS_GESTURE)
+ },
onDoneButtonClicked = closeTutorial,
+ lastSelectedScreen,
)
BACK_GESTURE ->
BackGestureTutorialScreen(
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractor.kt
index 66a598b7cf1b..c904ac5e3ae1 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractor.kt
@@ -35,6 +35,7 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.runningReduce
import kotlinx.coroutines.flow.stateIn
private const val DEFAULT_STREAM = AudioManager.STREAM_MUSIC
@@ -54,13 +55,17 @@ constructor(
volumeDialogStateInteractor.volumeDialogState
.filter { it.streamModels.isNotEmpty() }
.map { stateModel ->
- stateModel.streamModels.values
- .filter { streamModel -> shouldShowSliders(stateModel, streamModel) }
- .sortedWith(streamsSorter)
+ val sliderTypes =
+ stateModel.streamModels.values
+ .filter { streamModel -> shouldShowSliders(stateModel, streamModel) }
+ .sortedWith(streamsSorter)
+ .map { model -> model.toType() }
+ LinkedHashSet(sliderTypes)
}
- .map { models ->
- val sliderTypes: List<VolumeDialogSliderType> =
- models.map { model -> model.toType() }
+ .runningReduce { sliderTypes, newSliderTypes ->
+ newSliderTypes.apply { addAll(sliderTypes) }
+ }
+ .map { sliderTypes ->
VolumeDialogSlidersModel(
slider = sliderTypes.first(),
floatingSliders = sliderTypes.drop(1),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt
index 6e9b24f1433e..4ca84c58f6d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupHelperTest.kt
@@ -37,7 +37,6 @@ import com.google.common.truth.Truth.assertThat
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
-import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -63,22 +62,18 @@ class CommunalBackupHelperTest : SysuiTestCase() {
Room.inMemoryDatabaseBuilder(context, CommunalDatabase::class.java)
.allowMainThreadQueries()
.build()
+ onTeardown { database.close() }
CommunalDatabase.setInstance(database)
dao = database.communalWidgetDao()
backupUtils = CommunalBackupUtils(context)
backupDataFile = File(context.cacheDir, "backup_data_file")
+ onTeardown { backupDataFile.delete() }
underTest = CommunalBackupHelper(UserHandle.SYSTEM, backupUtils)
}
- @After
- fun teardown() {
- backupDataFile.delete()
- database.close()
- }
-
@Test
@EnableFlags(Flags.FLAG_COMMUNAL_HUB)
fun backupAndRestoreCommunalHub() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt
index f8d848139039..a9b6dd16cd95 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt
@@ -26,6 +26,7 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
import com.android.systemui.keyboard.shortcut.shortcutCustomizationViewModelFactory
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.google.common.truth.Truth.assertThat
@@ -37,7 +38,7 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class ShortcutCustomizationViewModelTest : SysuiTestCase() {
- private val kosmos = Kosmos()
+ private val kosmos = Kosmos().also { it.testCase = this }
private val testScope = kosmos.testScope
private val viewModel = kosmos.shortcutCustomizationViewModelFactory.create()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
index 7383faf2fd78..feae901114e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
@@ -278,7 +278,11 @@ class ShortcutHelperViewModelTest : SysuiTestCase() {
testScope.runTest {
fakeSystemSource.setGroups(
groupWithShortcutLabels("first Foo shortcut1", "first bar shortcut1"),
- groupWithShortcutLabels("second foO shortcut2", "second bar shortcut2"),
+ groupWithShortcutLabels(
+ "second foO shortcut2",
+ "second bar shortcut2",
+ groupLabel = SECOND_SIMPLE_GROUP_LABEL,
+ ),
)
fakeMultiTaskingSource.setGroups(
groupWithShortcutLabels("third FoO shortcut1", "third bar shortcut1")
@@ -298,7 +302,10 @@ class ShortcutHelperViewModelTest : SysuiTestCase() {
ShortcutCategory(
System,
subCategoryWithShortcutLabels("first Foo shortcut1"),
- subCategoryWithShortcutLabels("second foO shortcut2"),
+ subCategoryWithShortcutLabels(
+ "second foO shortcut2",
+ subCategoryLabel = SECOND_SIMPLE_GROUP_LABEL,
+ ),
),
),
ShortcutCategoryUi(
@@ -380,16 +387,23 @@ class ShortcutHelperViewModelTest : SysuiTestCase() {
assertThat(activeUiState.defaultSelectedCategory).isInstanceOf(CurrentApp::class.java)
}
- private fun groupWithShortcutLabels(vararg shortcutLabels: String) =
- KeyboardShortcutGroup(SIMPLE_GROUP_LABEL, shortcutLabels.map { simpleShortcutInfo(it) })
- .apply { packageName = "test.package.name" }
+ private fun groupWithShortcutLabels(
+ vararg shortcutLabels: String,
+ groupLabel: String = FIRST_SIMPLE_GROUP_LABEL,
+ ) =
+ KeyboardShortcutGroup(groupLabel, shortcutLabels.map { simpleShortcutInfo(it) }).apply {
+ packageName = "test.package.name"
+ }
private fun simpleShortcutInfo(label: String) =
KeyboardShortcutInfo(label, KeyEvent.KEYCODE_A, KeyEvent.META_CTRL_ON)
- private fun subCategoryWithShortcutLabels(vararg shortcutLabels: String) =
+ private fun subCategoryWithShortcutLabels(
+ vararg shortcutLabels: String,
+ subCategoryLabel: String = FIRST_SIMPLE_GROUP_LABEL,
+ ) =
ShortcutSubCategory(
- label = SIMPLE_GROUP_LABEL,
+ label = subCategoryLabel,
shortcuts = shortcutLabels.map { simpleShortcut(it) },
)
@@ -402,6 +416,7 @@ class ShortcutHelperViewModelTest : SysuiTestCase() {
}
companion object {
- private const val SIMPLE_GROUP_LABEL = "simple group"
+ private const val FIRST_SIMPLE_GROUP_LABEL = "simple group 1"
+ private const val SECOND_SIMPLE_GROUP_LABEL = "simple group 2"
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 856333ea724e..aad8b4ba1191 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -2707,6 +2707,44 @@ public class BubblesTest extends SysuiTestCase {
eq(BubbleLogger.Event.BUBBLE_BAR_EXPANDED));
}
+ @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+ @Test
+ public void testEventLogging_bubbleBar_openOverflow() {
+ mBubbleProperties.mIsBubbleBarEnabled = true;
+ mPositioner.setIsLargeScreen(true);
+ FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+ mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+ mEntryListener.onEntryAdded(mRow);
+
+ clearInvocations(mBubbleLogger);
+ mBubbleController.expandStackAndSelectBubbleFromLauncher(BubbleOverflow.KEY, 0);
+ verify(mBubbleLogger).log(BubbleLogger.Event.BUBBLE_BAR_OVERFLOW_SELECTED);
+ verifyNoMoreInteractions(mBubbleLogger);
+ }
+
+ @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+ @Test
+ public void testEventLogging_bubbleBar_fromOverflowToBar() {
+ mBubbleProperties.mIsBubbleBarEnabled = true;
+ mPositioner.setIsLargeScreen(true);
+ FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+ mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+ mEntryListener.onEntryAdded(mRow);
+
+ // Dismiss the bubble so it's in the overflow
+ mBubbleController.removeBubble(
+ mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
+ Bubble overflowBubble = mBubbleData.getOverflowBubbleWithKey(mRow.getKey());
+ assertThat(overflowBubble).isNotNull();
+
+ // Promote overflow bubble and check that it is logged
+ mBubbleController.promoteBubbleFromOverflow(overflowBubble);
+ verify(mBubbleLogger).log(eqBubbleWithKey(overflowBubble.getKey()),
+ eq(BubbleLogger.Event.BUBBLE_BAR_OVERFLOW_REMOVE_BACK_TO_BAR));
+ }
+
/** Creates a bubble using the userId and package. */
private Bubble createBubble(int userId, String pkg) {
final UserHandle userHandle = new UserHandle(userId);
diff --git a/packages/SystemUI/tests/utils/src/android/content/ClipboardManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/content/ClipboardManagerKosmos.kt
new file mode 100644
index 000000000000..379c00842b62
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/content/ClipboardManagerKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 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.content
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
+
+val Kosmos.clipboardManager by Kosmos.Fixture { mock<ClipboardManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/development/data/repository/DevelopmentSettingRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/development/data/repository/DevelopmentSettingRepositoryKosmos.kt
new file mode 100644
index 000000000000..3ce119576096
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/development/data/repository/DevelopmentSettingRepositoryKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 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.development.data.repository
+
+import android.os.userManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.util.settings.fakeGlobalSettings
+
+val Kosmos.developmentSettingRepository by
+ Kosmos.Fixture { DevelopmentSettingRepository(fakeGlobalSettings, userManager, testDispatcher) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/development/domain/interactor/BuildNumberInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/development/domain/interactor/BuildNumberInteractorKosmos.kt
new file mode 100644
index 000000000000..aa4dd18a6cba
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/development/domain/interactor/BuildNumberInteractorKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 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.development.domain.interactor
+
+import android.content.clipboardManager
+import android.content.res.mainResources
+import com.android.systemui.development.data.repository.developmentSettingRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.user.data.repository.userRepository
+
+val Kosmos.buildNumberInteractor by
+ Kosmos.Fixture {
+ BuildNumberInteractor(
+ developmentSettingRepository,
+ mainResources,
+ userRepository,
+ { clipboardManager },
+ testDispatcher,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/development/ui/viewmodel/BuildNumberViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/development/ui/viewmodel/BuildNumberViewModelKosmos.kt
new file mode 100644
index 000000000000..c827311a6ac3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/development/ui/viewmodel/BuildNumberViewModelKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 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.development.ui.viewmodel
+
+import com.android.systemui.development.domain.interactor.buildNumberInteractor
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.buildNumberViewModelFactory by
+ Kosmos.Fixture {
+ object : BuildNumberViewModel.Factory {
+ override fun create(): BuildNumberViewModel {
+ return BuildNumberViewModel(buildNumberInteractor)
+ }
+ }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
index 903bc8ebf42d..9cb15c5b816d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
@@ -136,7 +136,11 @@ val Kosmos.shortcutHelperStateInteractor by
}
val Kosmos.shortcutHelperCategoriesInteractor by
- Kosmos.Fixture { ShortcutHelperCategoriesInteractor(defaultShortcutCategoriesRepository) }
+ Kosmos.Fixture {
+ ShortcutHelperCategoriesInteractor(defaultShortcutCategoriesRepository) {
+ customShortcutCategoriesRepository
+ }
+ }
val Kosmos.shortcutHelperViewModel by
Kosmos.Fixture {
@@ -162,7 +166,8 @@ val Kosmos.shortcutCustomizationDialogStarterFactory by
}
}
-val Kosmos.shortcutCustomizationInteractor by Kosmos.Fixture { ShortcutCustomizationInteractor() }
+val Kosmos.shortcutCustomizationInteractor by
+ Kosmos.Fixture { ShortcutCustomizationInteractor(customShortcutCategoriesRepository) }
val Kosmos.shortcutCustomizationViewModelFactory by
Kosmos.Fixture {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelKosmos.kt
index d33d594d9e8a..76e2cc8b7bd0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelKosmos.kt
@@ -19,6 +19,7 @@ package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.keyguard.domain.interactor.keyguardSmartspaceInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.util.mockito.mock
val Kosmos.keyguardSmartspaceViewModel by
@@ -28,5 +29,6 @@ val Kosmos.keyguardSmartspaceViewModel by
smartspaceController = mock(),
keyguardClockViewModel = keyguardClockViewModel,
smartspaceInteractor = keyguardSmartspaceInteractor,
+ shadeInteractor = shadeInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt
index 0e5edb75846d..2e80293eafff 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.panels.ui.viewmodel
+import com.android.systemui.development.ui.viewmodel.buildNumberViewModelFactory
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.qs.panels.domain.interactor.paginatedGridInteractor
@@ -26,5 +27,6 @@ val Kosmos.paginatedGridViewModel by
qsColumnsViewModelFactory,
paginatedGridInteractor,
inFirstPageViewModel,
+ buildNumberViewModelFactory,
)
}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
index 869d854f7b23..9b71f8050c80 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
@@ -30,6 +30,8 @@ import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.ravenwood.common.RavenwoodCommonUtils;
+
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runner.Runner;
@@ -229,7 +231,9 @@ public final class RavenwoodAwareTestRunner extends RavenwoodAwareTestRunnerBase
s.evaluate();
onAfter(description, scope, order, null);
} catch (Throwable t) {
- if (onAfter(description, scope, order, t)) {
+ var shouldReportFailure = RavenwoodCommonUtils.runIgnoringException(
+ () -> onAfter(description, scope, order, t));
+ if (shouldReportFailure == null || shouldReportFailure) {
throw t;
}
}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
index 678a97be60a2..1c1f15761329 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -22,6 +22,8 @@ import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_INST_R
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_RESOURCE_APK;
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING;
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERSION_JAVA_SYSPROP;
+import static com.android.ravenwood.common.RavenwoodCommonUtils.parseNullableInt;
+import static com.android.ravenwood.common.RavenwoodCommonUtils.withDefault;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
@@ -39,6 +41,7 @@ import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
import android.os.Binder;
import android.os.Build;
+import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.HandlerThread;
import android.os.Looper;
@@ -154,6 +157,13 @@ public class RavenwoodRuntimeEnvironmentController {
private static RavenwoodAwareTestRunner sRunner;
private static RavenwoodSystemProperties sProps;
+ private static final int DEFAULT_TARGET_SDK_LEVEL = VERSION_CODES.CUR_DEVELOPMENT;
+ private static final String DEFAULT_PACKAGE_NAME = "com.android.ravenwoodtests.defaultname";
+
+ private static int sTargetSdkLevel;
+ private static String sTestPackageName;
+ private static String sTargetPackageName;
+
/**
* Initialize the global environment.
*/
@@ -235,9 +245,22 @@ public class RavenwoodRuntimeEnvironmentController {
System.setProperty("android.junit.runner",
"androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner");
+ loadRavenwoodProperties();
+
assertMockitoVersion();
}
+ private static void loadRavenwoodProperties() {
+ var props = RavenwoodSystemProperties.readProperties("ravenwood.properties");
+
+ sTargetSdkLevel = withDefault(
+ parseNullableInt(props.get("targetSdkVersionInt")), DEFAULT_TARGET_SDK_LEVEL);
+ sTargetPackageName = withDefault(props.get("packageName"), DEFAULT_PACKAGE_NAME);
+ sTestPackageName = withDefault(props.get("instPackageName"), sTargetPackageName);
+
+ // TODO(b/377765941) Read them from the manifest too?
+ }
+
/**
* Initialize the environment.
*/
@@ -256,7 +279,9 @@ public class RavenwoodRuntimeEnvironmentController {
initInner(runner.mState.getConfig());
} catch (Exception th) {
Log.e(TAG, "init() failed", th);
- reset();
+
+ RavenwoodCommonUtils.runIgnoringException(()-> reset());
+
SneakyThrow.sneakyThrow(th);
}
}
@@ -267,6 +292,14 @@ public class RavenwoodRuntimeEnvironmentController {
Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler);
}
+ config.mTargetPackageName = sTargetPackageName;
+ config.mTestPackageName = sTestPackageName;
+ config.mTargetSdkLevel = sTargetSdkLevel;
+
+ Log.i(TAG, "TargetPackageName=" + sTargetPackageName);
+ Log.i(TAG, "TestPackageName=" + sTestPackageName);
+ Log.i(TAG, "TargetSdkLevel=" + sTargetSdkLevel);
+
RavenwoodRuntimeState.sUid = config.mUid;
RavenwoodRuntimeState.sPid = config.mPid;
RavenwoodRuntimeState.sTargetSdkLevel = config.mTargetSdkLevel;
@@ -349,8 +382,11 @@ public class RavenwoodRuntimeEnvironmentController {
* Partially re-initialize after each test method invocation
*/
public static void reinit() {
- var config = sRunner.mState.getConfig();
- Binder.restoreCallingIdentity(packBinderIdentityToken(false, config.mUid, config.mPid));
+ // sRunner could be null, if there was a failure in the initialization.
+ if (sRunner != null) {
+ var config = sRunner.mState.getConfig();
+ Binder.restoreCallingIdentity(packBinderIdentityToken(false, config.mUid, config.mPid));
+ }
}
private static void initializeCompatIds(RavenwoodConfig config) {
@@ -380,6 +416,9 @@ public class RavenwoodRuntimeEnvironmentController {
/**
* De-initialize.
+ *
+ * Note, we call this method when init() fails too, so this method should deal with
+ * any partially-initialized states.
*/
public static void reset() {
if (RAVENWOOD_VERBOSE_LOGGING) {
@@ -411,7 +450,9 @@ public class RavenwoodRuntimeEnvironmentController {
config.mState.mSystemServerContext.cleanUp();
}
- Looper.getMainLooper().quit();
+ if (Looper.getMainLooper() != null) {
+ Looper.getMainLooper().quit();
+ }
Looper.clearMainLooperForTest();
ActivityManager.reset$ravenwood();
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
index 3ed8b0a748e1..7ca9239d2062 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
@@ -22,7 +22,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Instrumentation;
import android.content.Context;
-import android.os.Build;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -30,16 +29,12 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.List;
-import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
/**
- * Represents how to configure the ravenwood environment for a test class.
- *
- * If a ravenwood test class has a public static field with the {@link Config} annotation,
- * Ravenwood will extract the config from it and initializes the environment. The type of the
- * field must be of {@link RavenwoodConfig}.
+ * @deprecated This class will be removed. Reach out to g/ravenwood if you need any features in it.
*/
+@Deprecated
public final class RavenwoodConfig {
/**
* Use this to mark a field as the configuration.
@@ -66,7 +61,7 @@ public final class RavenwoodConfig {
String mTestPackageName;
String mTargetPackageName;
- int mTargetSdkLevel = Build.VERSION_CODES.CUR_DEVELOPMENT;
+ int mTargetSdkLevel;
final RavenwoodSystemProperties mSystemProperties = new RavenwoodSystemProperties();
@@ -91,12 +86,6 @@ public final class RavenwoodConfig {
return RavenwoodRule.isOnRavenwood();
}
- private void setDefaults() {
- if (mTargetPackageName == null) {
- mTargetPackageName = mTestPackageName;
- }
- }
-
public static class Builder {
private final RavenwoodConfig mConfig = new RavenwoodConfig();
@@ -120,28 +109,27 @@ public final class RavenwoodConfig {
}
/**
- * Configure the package name of the test, which corresponds to
- * {@link Instrumentation#getContext()}.
+ * @deprecated no longer used. Package name is set in the build file. (for now)
*/
+ @Deprecated
public Builder setPackageName(@NonNull String packageName) {
- mConfig.mTestPackageName = Objects.requireNonNull(packageName);
return this;
}
/**
- * Configure the package name of the target app, which corresponds to
- * {@link Instrumentation#getTargetContext()}. Defaults to {@link #setPackageName}.
+ * @deprecated no longer used. Package name is set in the build file. (for now)
*/
+ @Deprecated
public Builder setTargetPackageName(@NonNull String packageName) {
- mConfig.mTargetPackageName = Objects.requireNonNull(packageName);
return this;
}
+
/**
- * Configure the target SDK level of the test.
+ * @deprecated no longer used. Target SDK level is set in the build file. (for now)
*/
+ @Deprecated
public Builder setTargetSdkLevel(int sdkLevel) {
- mConfig.mTargetSdkLevel = sdkLevel;
return this;
}
@@ -154,34 +142,32 @@ public final class RavenwoodConfig {
}
/**
- * Configure the given system property as immutable for the duration of the test.
- * Read access to the key is allowed, and write access will fail. When {@code value} is
- * {@code null}, the value is left as undefined.
- *
- * All properties in the {@code debug.*} namespace are automatically mutable, with no
- * developer action required.
- *
- * Has no effect on non-Ravenwood environments.
+ * @deprecated Use {@link RavenwoodRule.Builder#setSystemPropertyImmutable(String, Object)}
*/
+ @Deprecated
public Builder setSystemPropertyImmutable(@NonNull String key,
@Nullable Object value) {
- mConfig.mSystemProperties.setValue(key, value);
- mConfig.mSystemProperties.setAccessReadOnly(key);
return this;
}
/**
- * Configure the given system property as mutable for the duration of the test.
- * Both read and write access to the key is allowed, and its value will be reset between
- * each test. When {@code value} is {@code null}, the value is left as undefined.
- *
- * All properties in the {@code debug.*} namespace are automatically mutable, with no
- * developer action required.
- *
- * Has no effect on non-Ravenwood environments.
+ * @deprecated Use {@link RavenwoodRule.Builder#setSystemPropertyMutable(String, Object)}
*/
+ @Deprecated
public Builder setSystemPropertyMutable(@NonNull String key,
@Nullable Object value) {
+ return this;
+ }
+
+ Builder setSystemPropertyImmutableReal(@NonNull String key,
+ @Nullable Object value) {
+ mConfig.mSystemProperties.setValue(key, value);
+ mConfig.mSystemProperties.setAccessReadOnly(key);
+ return this;
+ }
+
+ Builder setSystemPropertyMutableReal(@NonNull String key,
+ @Nullable Object value) {
mConfig.mSystemProperties.setValue(key, value);
mConfig.mSystemProperties.setAccessReadWrite(key);
return this;
@@ -205,7 +191,6 @@ public final class RavenwoodConfig {
}
public RavenwoodConfig build() {
- mConfig.setDefaults();
return mConfig;
}
}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index bfa3802ce583..5681a9040f63 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -36,10 +36,8 @@ import java.util.Objects;
import java.util.regex.Pattern;
/**
- * @deprecated Use {@link RavenwoodConfig} to configure the ravenwood environment instead.
- * A {@link RavenwoodRule} is no longer needed for {@link DisabledOnRavenwood}. To get the
- * {@link Context} and {@link Instrumentation}, use
- * {@link androidx.test.platform.app.InstrumentationRegistry} instead.
+ * @deprecated This class is undergoing a major change. Reach out to g/ravenwood if you need
+ * any featues in it.
*/
@Deprecated
public final class RavenwoodRule implements TestRule {
@@ -128,11 +126,10 @@ public final class RavenwoodRule implements TestRule {
}
/**
- * Configure the identity of this process to be the given package name for the duration
- * of the test. Has no effect on non-Ravenwood environments.
+ * @deprecated no longer used.
*/
+ @Deprecated
public Builder setPackageName(@NonNull String packageName) {
- mBuilder.setPackageName(packageName);
return this;
}
@@ -155,7 +152,7 @@ public final class RavenwoodRule implements TestRule {
* Has no effect on non-Ravenwood environments.
*/
public Builder setSystemPropertyImmutable(@NonNull String key, @Nullable Object value) {
- mBuilder.setSystemPropertyImmutable(key, value);
+ mBuilder.setSystemPropertyImmutableReal(key, value);
return this;
}
@@ -170,7 +167,7 @@ public final class RavenwoodRule implements TestRule {
* Has no effect on non-Ravenwood environments.
*/
public Builder setSystemPropertyMutable(@NonNull String key, @Nullable Object value) {
- mBuilder.setSystemPropertyMutable(key, value);
+ mBuilder.setSystemPropertyMutableReal(key, value);
return this;
}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
index 3e4619f55c6d..9bd376a76f77 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
@@ -52,7 +52,7 @@ public class RavenwoodSystemProperties {
"vendor_dlkm",
};
- private static Map<String, String> readProperties(String propFile) {
+ static Map<String, String> readProperties(String propFile) {
// Use an ordered map just for cleaner dump log.
final Map<String, String> ret = new LinkedHashMap<>();
try {
@@ -60,7 +60,7 @@ public class RavenwoodSystemProperties {
.map(String::trim)
.filter(s -> !s.startsWith("#"))
.map(s -> s.split("\\s*=\\s*", 2))
- .filter(a -> a.length == 2)
+ .filter(a -> a.length == 2 && a[1].length() > 0)
.forEach(a -> ret.put(a[0], a[1]));
} catch (IOException e) {
throw new RuntimeException(e);
diff --git a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
index 520f050f0655..2a04d4469ef4 100644
--- a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
+++ b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
@@ -30,6 +30,7 @@ import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
+import java.util.function.Supplier;
public class RavenwoodCommonUtils {
private static final String TAG = "RavenwoodCommonUtils";
@@ -277,11 +278,55 @@ public class RavenwoodCommonUtils {
(isStatic ? "static" : "")));
}
+ /**
+ * Run a supplier and swallow the exception, if any.
+ *
+ * It's a dangerous function. Only use it in an exception handler where we don't want to crash.
+ */
+ @Nullable
+ public static <T> T runIgnoringException(@NonNull Supplier<T> s) {
+ try {
+ return s.get();
+ } catch (Throwable th) {
+ log(TAG, "Warning: Exception detected! " + getStackTraceString(th));
+ }
+ return null;
+ }
+
+ /**
+ * Run a runnable and swallow the exception, if any.
+ *
+ * It's a dangerous function. Only use it in an exception handler where we don't want to crash.
+ */
+ public static void runIgnoringException(@NonNull Runnable r) {
+ runIgnoringException(() -> {
+ r.run();
+ return null;
+ });
+ }
+
@NonNull
- public static String getStackTraceString(@Nullable Throwable th) {
+ public static String getStackTraceString(@NonNull Throwable th) {
StringWriter stringWriter = new StringWriter();
PrintWriter writer = new PrintWriter(stringWriter);
th.printStackTrace(writer);
return stringWriter.toString();
}
+
+ /** Same as {@link Integer#parseInt(String)} but accepts null and returns null. */
+ @Nullable
+ public static Integer parseNullableInt(@Nullable String value) {
+ if (value == null) {
+ return null;
+ }
+ return Integer.parseInt(value);
+ }
+
+ /**
+ * @return {@code value} if it's non-null. Otherwise, returns {@code def}.
+ */
+ @Nullable
+ public static <T> T withDefault(@Nullable T value, @Nullable T def) {
+ return value != null ? value : def;
+ }
}
diff --git a/ravenwood/tests/bivalentinst/Android.bp b/ravenwood/tests/bivalentinst/Android.bp
index 41e45e5a6d95..31e3bcc3634f 100644
--- a/ravenwood/tests/bivalentinst/Android.bp
+++ b/ravenwood/tests/bivalentinst/Android.bp
@@ -27,6 +27,9 @@ android_ravenwood_test {
"junit",
"truth",
],
+
+ package_name: "com.android.ravenwood.bivalentinsttest_self_inst",
+
resource_apk: "RavenwoodBivalentInstTest_self_inst_device",
auto_gen_config: true,
}
@@ -53,6 +56,10 @@ android_ravenwood_test {
"truth",
],
resource_apk: "RavenwoodBivalentInstTestTarget",
+
+ package_name: "com.android.ravenwood.bivalentinst_target_app",
+ inst_package_name: "com.android.ravenwood.bivalentinsttest_nonself_inst",
+
inst_resource_apk: "RavenwoodBivalentInstTest_nonself_inst_device",
auto_gen_config: true,
}
diff --git a/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_nonself.java b/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_nonself.java
index 92d43d714e14..db252d87c784 100644
--- a/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_nonself.java
+++ b/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_nonself.java
@@ -19,8 +19,6 @@ import static com.google.common.truth.Truth.assertThat;
import android.app.Instrumentation;
import android.content.Context;
-import android.platform.test.ravenwood.RavenwoodConfig;
-import android.platform.test.ravenwood.RavenwoodConfig.Config;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -39,11 +37,6 @@ public class RavenwoodInstrumentationTest_nonself {
private static final String TEST_PACKAGE_NAME =
"com.android.ravenwood.bivalentinsttest_nonself_inst";
- @Config
- public static final RavenwoodConfig sConfig = new RavenwoodConfig.Builder()
- .setPackageName(TEST_PACKAGE_NAME)
- .setTargetPackageName(TARGET_PACKAGE_NAME)
- .build();
private static Instrumentation sInstrumentation;
private static Context sTestContext;
diff --git a/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_self.java b/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_self.java
index 2f35923dead2..94b18619efcd 100644
--- a/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_self.java
+++ b/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_self.java
@@ -19,8 +19,6 @@ import static com.google.common.truth.Truth.assertThat;
import android.app.Instrumentation;
import android.content.Context;
-import android.platform.test.ravenwood.RavenwoodConfig;
-import android.platform.test.ravenwood.RavenwoodConfig.Config;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -40,13 +38,6 @@ public class RavenwoodInstrumentationTest_self {
private static final String TEST_PACKAGE_NAME =
"com.android.ravenwood.bivalentinsttest_self_inst";
- @Config
- public static final RavenwoodConfig sConfig = new RavenwoodConfig.Builder()
- .setPackageName(TEST_PACKAGE_NAME)
- .setTargetPackageName(TARGET_PACKAGE_NAME)
- .build();
-
-
private static Instrumentation sInstrumentation;
private static Context sTestContext;
private static Context sTargetContext;
diff --git a/ravenwood/tests/bivalenttest/Android.bp b/ravenwood/tests/bivalenttest/Android.bp
index 40e6672a3c63..ac545dfb06cc 100644
--- a/ravenwood/tests/bivalenttest/Android.bp
+++ b/ravenwood/tests/bivalenttest/Android.bp
@@ -84,6 +84,8 @@ java_defaults {
android_ravenwood_test {
name: "RavenwoodBivalentTest",
defaults: ["ravenwood-bivalent-defaults"],
+ target_sdk_version: "34",
+ package_name: "com.android.ravenwoodtest.bivalenttest",
auto_gen_config: true,
}
diff --git a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodConfigTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodConfigTest.java
index a5a16c14600b..306c2b39c70d 100644
--- a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodConfigTest.java
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodConfigTest.java
@@ -20,8 +20,6 @@ import static android.platform.test.ravenwood.RavenwoodConfig.isOnRavenwood;
import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeTrue;
-import android.platform.test.ravenwood.RavenwoodConfig;
-
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -33,13 +31,7 @@ import org.junit.runner.RunWith;
*/
@RunWith(AndroidJUnit4.class)
public class RavenwoodConfigTest {
- private static final String PACKAGE_NAME = "com.test";
-
- @RavenwoodConfig.Config
- public static RavenwoodConfig sConfig =
- new RavenwoodConfig.Builder()
- .setPackageName(PACKAGE_NAME)
- .build();
+ private static final String PACKAGE_NAME = "com.android.ravenwoodtest.bivalenttest";
@Test
public void testConfig() {
diff --git a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/compat/RavenwoodCompatFrameworkTest.kt b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/compat/RavenwoodCompatFrameworkTest.kt
index a95760db1a61..882c91c43ee9 100644
--- a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/compat/RavenwoodCompatFrameworkTest.kt
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/compat/RavenwoodCompatFrameworkTest.kt
@@ -16,8 +16,6 @@
package com.android.ravenwoodtest.bivalenttest.compat
import android.app.compat.CompatChanges
-import android.os.Build
-import android.platform.test.ravenwood.RavenwoodConfig
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.internal.ravenwood.RavenwoodEnvironment.CompatIdsForTest
import org.junit.Assert
@@ -26,14 +24,6 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class RavenwoodCompatFrameworkTest {
- companion object {
- @JvmField // Expose as a raw field, not as a property.
- @RavenwoodConfig.Config
- val config = RavenwoodConfig.Builder()
- .setTargetSdkLevel(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- .build()
- }
-
@Test
fun testEnabled() {
Assert.assertTrue(CompatChanges.isChangeEnabled(CompatIdsForTest.TEST_COMPAT_ID_1))
@@ -53,4 +43,4 @@ class RavenwoodCompatFrameworkTest {
fun testEnabledAfterUForUApps() {
Assert.assertFalse(CompatChanges.isChangeEnabled(CompatIdsForTest.TEST_COMPAT_ID_4))
}
-} \ No newline at end of file
+}
diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java
index 02d10732245d..f94b98bc1fb8 100644
--- a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java
+++ b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java
@@ -24,6 +24,7 @@ import android.platform.test.ravenwood.RavenwoodRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
@@ -32,6 +33,10 @@ import org.junit.runner.RunWith;
/**
* Test for @Config field extraction and validation.
+ *
+ * TODO(b/377765941) Most of the tests here will be obsolete and deleted with b/377765941, but
+ * some of the tests may need to be re-implemented one way or another. (e.g. the package name
+ * test.) Until that happens, we'll keep all tests here but add an {@code @Ignore} instead.
*/
@NoRavenizer // This class shouldn't be executed with RavenwoodAwareTestRunner.
public class RavenwoodRunnerConfigValidationTest extends RavenwoodRunnerTestBase {
@@ -59,6 +64,7 @@ public class RavenwoodRunnerConfigValidationTest extends RavenwoodRunnerTestBase
testRunFinished: 1,0,0,0
""")
// CHECKSTYLE:ON
+ @Ignore // Package name is no longer set via config.
public static class ConfigInBaseClassTest extends ConfigInBaseClass {
@Test
public void test() {
@@ -83,6 +89,7 @@ public class RavenwoodRunnerConfigValidationTest extends RavenwoodRunnerTestBase
testRunFinished: 1,0,0,0
""")
// CHECKSTYLE:ON
+ @Ignore // Package name is no longer set via config.
public static class ConfigOverridingTest extends ConfigInBaseClass {
static String PACKAGE_NAME_OVERRIDE = "com.ConfigOverridingTest";
@@ -376,6 +383,7 @@ public class RavenwoodRunnerConfigValidationTest extends RavenwoodRunnerTestBase
testRunFinished: 1,0,0,0
""")
// CHECKSTYLE:ON
+ @Ignore // Package name is no longer set via config.
public static class RuleInBaseClassSuccessTest extends RuleInBaseClass {
@Test
@@ -437,6 +445,7 @@ public class RavenwoodRunnerConfigValidationTest extends RavenwoodRunnerTestBase
testRunFinished: 1,1,0,0
""")
// CHECKSTYLE:ON
+ @Ignore // Package name is no longer set via config.
public static class RuleWithDifferentTypeInBaseClassSuccessTest extends RuleWithDifferentTypeInBaseClass {
@Test
diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerTestBase.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerTestBase.java
index f7a2198a9bc4..0e3d053e90b1 100644
--- a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerTestBase.java
+++ b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerTestBase.java
@@ -25,6 +25,8 @@ import android.util.Log;
import junitparams.JUnitParamsRunner;
import junitparams.Parameters;
+
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.Description;
import org.junit.runner.JUnitCore;
@@ -103,6 +105,7 @@ public abstract class RavenwoodRunnerTestBase {
var thisClass = this.getClass();
var ret = Arrays.stream(thisClass.getNestMembers())
.filter((c) -> c.getAnnotation(Expected.class) != null)
+ .filter((c) -> c.getAnnotation(Ignore.class) == null)
.toArray(Class[]::new);
assertThat(ret.length).isGreaterThan(0);
diff --git a/ravenwood/tests/runtime-test/Android.bp b/ravenwood/tests/runtime-test/Android.bp
index 0c0df1f993aa..c3520031ea7d 100644
--- a/ravenwood/tests/runtime-test/Android.bp
+++ b/ravenwood/tests/runtime-test/Android.bp
@@ -9,7 +9,7 @@ package {
android_ravenwood_test {
name: "RavenwoodRuntimeTest",
-
+ target_sdk_version: "34",
libs: [
"ravenwood-helper-runtime",
],
diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java
index e59bb42fd666..4f632c9e28a1 100644
--- a/services/autofill/java/com/android/server/autofill/Helper.java
+++ b/services/autofill/java/com/android/server/autofill/Helper.java
@@ -353,7 +353,10 @@ public final class Helper {
private static void addAutofillableIds(@NonNull ViewNode node,
@NonNull ArrayList<AutofillId> ids, boolean autofillableOnly) {
if (!autofillableOnly || node.getAutofillType() != View.AUTOFILL_TYPE_NONE) {
- ids.add(node.getAutofillId());
+ AutofillId id = node.getAutofillId();
+ if (id != null) {
+ ids.add(id);
+ }
}
final int size = node.getChildCount();
for (int i = 0; i < size; i++) {
diff --git a/services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java b/services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java
new file mode 100644
index 000000000000..5e4bab15952d
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2017 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.backup;
+
+import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
+import static com.android.server.backup.BackupManagerService.TAG;
+
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.ApplicationThreadConstants;
+import android.app.IActivityManager;
+import android.app.IBackupAgent;
+import android.app.backup.BackupAnnotations;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Binder;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+import com.android.server.backup.internal.LifecycleOperationStorage;
+
+import java.util.Set;
+
+/**
+ * Handles the lifecycle of {@link IBackupAgent}s that the {@link UserBackupManagerService}
+ * communicates with.
+ *
+ * <p>There can only be one agent that's connected to at a time.
+ *
+ * <p>There should be only one instance of this class per {@link UserBackupManagerService}.
+ */
+public class BackupAgentConnectionManager {
+
+ /**
+ * Enables the OS making a decision on whether backup restricted mode should be used for apps
+ * that haven't explicitly opted in or out. See
+ * {@link android.content.pm.PackageManager#PROPERTY_USE_RESTRICTED_BACKUP_MODE} for details.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.BAKLAVA)
+ public static final long OS_DECIDES_BACKUP_RESTRICTED_MODE = 376661510;
+
+ // The thread performing the sequence of queued backups binds to each app's agent
+ // in succession. Bind notifications are asynchronously delivered through the
+ // Activity Manager; use this lock object to signal when a requested binding has
+ // completed.
+ private final Object mAgentConnectLock = new Object();
+ private IBackupAgent mConnectedAgent;
+ private volatile boolean mConnecting;
+ private final ArraySet<String> mRestoreNoRestrictedModePackages = new ArraySet<>();
+ private final ArraySet<String> mBackupNoRestrictedModePackages = new ArraySet<>();
+
+ private final IActivityManager mActivityManager;
+ private final ActivityManagerInternal mActivityManagerInternal;
+ private final LifecycleOperationStorage mOperationStorage;
+ private final PackageManager mPackageManager;
+ private final UserBackupManagerService mUserBackupManagerService;
+ private final int mUserId;
+ private final String mUserIdMsg;
+
+ BackupAgentConnectionManager(LifecycleOperationStorage operationStorage,
+ PackageManager packageManager, UserBackupManagerService userBackupManagerService,
+ int userId) {
+ mActivityManager = ActivityManager.getService();
+ mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
+ mOperationStorage = operationStorage;
+ mPackageManager = packageManager;
+ mUserBackupManagerService = userBackupManagerService;
+ mUserId = userId;
+ mUserIdMsg = "[UserID:" + userId + "] ";
+ }
+
+ /**
+ * Fires off a backup agent, blocking until it attaches (and ActivityManager will call
+ * {@link #agentConnected(String, IBinder)}) or until this operation times out.
+ *
+ * @param mode a {@code BACKUP_MODE} from {@link android.app.ApplicationThreadConstants}.
+ */
+ @Nullable
+ public IBackupAgent bindToAgentSynchronous(ApplicationInfo app, int mode,
+ @BackupAnnotations.BackupDestination int backupDestination) {
+ IBackupAgent agent = null;
+ synchronized (mAgentConnectLock) {
+ mConnecting = true;
+ mConnectedAgent = null;
+ boolean useRestrictedMode = shouldUseRestrictedBackupModeForPackage(mode,
+ app.packageName);
+ try {
+ if (mActivityManager.bindBackupAgent(app.packageName, mode, mUserId,
+ backupDestination, useRestrictedMode)) {
+ Slog.d(TAG, mUserIdMsg + "awaiting agent for " + app);
+
+ // success; wait for the agent to arrive
+ // only wait 10 seconds for the bind to happen
+ long timeoutMark = System.currentTimeMillis() + 10 * 1000;
+ while (mConnecting && mConnectedAgent == null && (System.currentTimeMillis()
+ < timeoutMark)) {
+ try {
+ mAgentConnectLock.wait(5000);
+ } catch (InterruptedException e) {
+ // just bail
+ Slog.w(TAG, mUserIdMsg + "Interrupted: " + e);
+ mConnecting = false;
+ mConnectedAgent = null;
+ }
+ }
+
+ // if we timed out with no connect, abort and move on
+ if (mConnecting) {
+ Slog.w(TAG, mUserIdMsg + "Timeout waiting for agent " + app);
+ mConnectedAgent = null;
+ }
+ Slog.i(TAG, mUserIdMsg + "got agent " + mConnectedAgent);
+ agent = mConnectedAgent;
+ }
+ } catch (RemoteException e) {
+ // can't happen - ActivityManager is local
+ }
+ }
+ if (agent == null) {
+ mActivityManagerInternal.clearPendingBackup(mUserId);
+ }
+ return agent;
+ }
+
+ /**
+ * Tell the ActivityManager that we are done with the {@link IBackupAgent} of this {@code app}.
+ * It will tell the app to destroy the agent.
+ */
+ public void unbindAgent(ApplicationInfo app) {
+ try {
+ mActivityManager.unbindBackupAgent(app);
+ } catch (RemoteException e) {
+ // Can't happen - activity manager is local
+ }
+ }
+
+ /**
+ * Callback: a requested backup agent has been instantiated. This should only be called from
+ * the
+ * {@link ActivityManager} when it's telling us that an agent is ready after a call to
+ * {@link #bindToAgentSynchronous(ApplicationInfo, int, int)}.
+ */
+ public void agentConnected(String packageName, IBinder agentBinder) {
+ synchronized (mAgentConnectLock) {
+ if (getCallingUid() == android.os.Process.SYSTEM_UID) {
+ Slog.d(TAG,
+ mUserIdMsg + "agentConnected pkg=" + packageName + " agent=" + agentBinder);
+ mConnectedAgent = IBackupAgent.Stub.asInterface(agentBinder);
+ mConnecting = false;
+ } else {
+ Slog.w(TAG, mUserIdMsg + "Non-system process uid=" + getCallingUid()
+ + " claiming agent connected");
+ }
+ mAgentConnectLock.notifyAll();
+ }
+ }
+
+ /**
+ * Callback: a backup agent has failed to come up, or has unexpectedly quit. If the agent failed
+ * to come up in the first place, the agentBinder argument will be {@code null}. This should
+ * only be called from the {@link ActivityManager}.
+ */
+ public void agentDisconnected(String packageName) {
+ synchronized (mAgentConnectLock) {
+ if (getCallingUid() == Process.SYSTEM_UID) {
+ mConnectedAgent = null;
+ mConnecting = false;
+ } else {
+ Slog.w(TAG, mUserIdMsg + "Non-system process uid=" + getCallingUid()
+ + " claiming agent disconnected");
+ }
+ Slog.w(TAG, mUserIdMsg + "agentDisconnected: the backup agent for " + packageName
+ + " died: cancel current operations");
+
+ // Offload operation cancellation off the main thread as the cancellation callbacks
+ // might call out to BackupTransport. Other operations started on the same package
+ // before the cancellation callback has executed will also be cancelled by the callback.
+ Runnable cancellationRunnable = () -> {
+ // handleCancel() causes the PerformFullTransportBackupTask to go on to
+ // tearDownAgentAndKill: that will unbindBackupAgent in the Activity Manager, so
+ // that the package being backed up doesn't get stuck in restricted mode until the
+ // backup time-out elapses.
+ for (int token : mOperationStorage.operationTokensForPackage(packageName)) {
+ if (MORE_DEBUG) {
+ Slog.d(TAG,
+ mUserIdMsg + "agentDisconnected: will handleCancel(all) for token:"
+ + Integer.toHexString(token));
+ }
+ mUserBackupManagerService.handleCancel(token, true /* cancelAll */);
+ }
+ };
+ getThreadForCancellation(cancellationRunnable).start();
+
+ mAgentConnectLock.notifyAll();
+ }
+ }
+
+ /**
+ * Marks the given set of packages as packages that should not be put into restricted mode if
+ * they are started for the given {@link BackupAnnotations.OperationType}.
+ */
+ public void setNoRestrictedModePackages(Set<String> packageNames,
+ @BackupAnnotations.OperationType int opType) {
+ if (opType == BackupAnnotations.OperationType.BACKUP) {
+ mBackupNoRestrictedModePackages.clear();
+ mBackupNoRestrictedModePackages.addAll(packageNames);
+ } else if (opType == BackupAnnotations.OperationType.RESTORE) {
+ mRestoreNoRestrictedModePackages.clear();
+ mRestoreNoRestrictedModePackages.addAll(packageNames);
+ } else {
+ throw new IllegalArgumentException("opType must be BACKUP or RESTORE");
+ }
+ }
+
+ /**
+ * Clears the list of packages that should not be put into restricted mode for either backup or
+ * restore.
+ */
+ public void clearNoRestrictedModePackages() {
+ mBackupNoRestrictedModePackages.clear();
+ mRestoreNoRestrictedModePackages.clear();
+ }
+
+ /**
+ * If the app has specified {@link PackageManager#PROPERTY_USE_RESTRICTED_BACKUP_MODE}, then
+ * its value is returned. If it hasn't and it targets an SDK below
+ * {@link Build.VERSION_CODES#BAKLAVA} then returns true. If it targets a newer SDK, then
+ * returns the decision made by the {@link android.app.backup.BackupTransport}.
+ *
+ * <p>When this method is called, we should have already asked the transport and cached its
+ * response in {@link #mBackupNoRestrictedModePackages} or
+ * {@link #mRestoreNoRestrictedModePackages} so this method will immediately return without
+ * any IPC to the transport.
+ */
+ private boolean shouldUseRestrictedBackupModeForPackage(
+ @BackupAnnotations.OperationType int mode, String packageName) {
+ if (!Flags.enableRestrictedModeChanges()) {
+ return true;
+ }
+
+ // Key/Value apps are never put in restricted mode.
+ if (mode == ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL
+ || mode == ApplicationThreadConstants.BACKUP_MODE_RESTORE) {
+ return false;
+ }
+
+ try {
+ PackageManager.Property property = mPackageManager.getPropertyAsUser(
+ PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE,
+ packageName, /* className= */ null, mUserId);
+ if (property.isBoolean()) {
+ // If the package has explicitly specified, we won't ask the transport.
+ return property.getBoolean();
+ } else {
+ Slog.w(TAG,
+ PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE + "must be a boolean.");
+ }
+ } catch (NameNotFoundException e) {
+ // This is expected when the package has not defined the property in its manifest.
+ }
+
+ // The package has not specified the property. The behavior depends on the package's
+ // targetSdk.
+ // <36 gets the old behavior of always using restricted mode.
+ if (!CompatChanges.isChangeEnabled(OS_DECIDES_BACKUP_RESTRICTED_MODE, packageName,
+ UserHandle.of(mUserId))) {
+ return true;
+ }
+
+ // Apps targeting >=36 get the behavior decided by the transport.
+ // By this point, we should have asked the transport and cached its decision.
+ if ((mode == ApplicationThreadConstants.BACKUP_MODE_FULL
+ && mBackupNoRestrictedModePackages.contains(packageName)) || (
+ mode == ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL
+ && mRestoreNoRestrictedModePackages.contains(packageName))) {
+ Slog.d(TAG, "Transport requested no restricted mode for: " + packageName);
+ return false;
+ }
+ return true;
+ }
+
+ @VisibleForTesting
+ Thread getThreadForCancellation(Runnable operation) {
+ return new Thread(operation, /* operationName */ "agent-disconnected");
+ }
+
+ @VisibleForTesting
+ int getCallingUid() {
+ return Binder.getCallingUid();
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 5f0071d47c89..3f6ede95eaf9 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -658,7 +658,8 @@ public class BackupManagerService extends IBackupManager.Stub {
getServiceForUserIfCallerHasPermission(userId, "agentConnected()");
if (userBackupManagerService != null) {
- userBackupManagerService.agentConnected(packageName, agentBinder);
+ userBackupManagerService.getBackupAgentConnectionManager().agentConnected(packageName,
+ agentBinder);
}
}
@@ -683,7 +684,8 @@ public class BackupManagerService extends IBackupManager.Stub {
getServiceForUserIfCallerHasPermission(userId, "agentDisconnected()");
if (userBackupManagerService != null) {
- userBackupManagerService.agentDisconnected(packageName);
+ userBackupManagerService.getBackupAgentConnectionManager().agentDisconnected(
+ packageName);
}
}
diff --git a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
index f5d68362c70a..136bacdd6399 100644
--- a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
+++ b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
@@ -146,7 +146,8 @@ public class KeyValueAdbBackupEngine {
private IBackupAgent bindToAgent(ApplicationInfo targetApp) {
try {
- return mBackupManagerService.bindToAgentSynchronous(targetApp,
+ return mBackupManagerService.getBackupAgentConnectionManager().bindToAgentSynchronous(
+ targetApp,
ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL,
BackupAnnotations.BackupDestination.CLOUD);
} catch (SecurityException e) {
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 5de2fb30ac78..e085f6e63067 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -43,9 +43,7 @@ import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AlarmManager;
import android.app.AppGlobals;
-import android.app.ApplicationThreadConstants;
import android.app.IActivityManager;
-import android.app.IBackupAgent;
import android.app.PendingIntent;
import android.app.backup.BackupAgent;
import android.app.backup.BackupAnnotations;
@@ -60,9 +58,6 @@ import android.app.backup.IBackupObserver;
import android.app.backup.IFullBackupRestoreObserver;
import android.app.backup.IRestoreSession;
import android.app.backup.ISelectBackupTransportCallback;
-import android.app.compat.CompatChanges;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledSince;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -83,7 +78,6 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
-import android.os.IBinder;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
@@ -302,21 +296,10 @@ public class UserBackupManagerService {
private static final String BACKUP_FINISHED_ACTION = "android.intent.action.BACKUP_FINISHED";
private static final String BACKUP_FINISHED_PACKAGE_EXTRA = "packageName";
- /**
- * Enables the OS making a decision on whether backup restricted mode should be used for apps
- * that haven't explicitly opted in or out. See
- * {@link PackageManager#PROPERTY_USE_RESTRICTED_BACKUP_MODE} for details.
- */
- @ChangeId
- @EnabledSince(targetSdkVersion = Build.VERSION_CODES.BAKLAVA)
- public static final long OS_DECIDES_BACKUP_RESTRICTED_MODE = 376661510;
-
// Time delay for initialization operations that can be delayed so as not to consume too much
// CPU on bring-up and increase time-to-UI.
private static final long INITIALIZATION_DELAY_MILLIS = 3000;
- // Timeout interval for deciding that a bind has taken too long.
- private static final long BIND_TIMEOUT_INTERVAL = 10 * 1000;
// Timeout interval for deciding that a clear-data has taken too long.
private static final long CLEAR_DATA_TIMEOUT_INTERVAL = 30 * 1000;
@@ -365,22 +348,9 @@ public class UserBackupManagerService {
// Backups that we haven't started yet. Keys are package names.
private final HashMap<String, BackupRequest> mPendingBackups = new HashMap<>();
- private final ArraySet<String> mRestoreNoRestrictedModePackages = new ArraySet<>();
- private final ArraySet<String> mBackupNoRestrictedModePackages = new ArraySet<>();
-
// locking around the pending-backup management
private final Object mQueueLock = new Object();
-
private final UserBackupPreferences mBackupPreferences;
-
- // The thread performing the sequence of queued backups binds to each app's agent
- // in succession. Bind notifications are asynchronously delivered through the
- // Activity Manager; use this lock object to signal when a requested binding has
- // completed.
- private final Object mAgentConnectLock = new Object();
- private IBackupAgent mConnectedAgent;
- private volatile boolean mConnecting;
-
private volatile boolean mBackupRunning;
private volatile long mLastBackupPass;
@@ -410,6 +380,7 @@ public class UserBackupManagerService {
private ActiveRestoreSession mActiveRestoreSession;
+ private final BackupAgentConnectionManager mBackupAgentConnectionManager;
private final LifecycleOperationStorage mOperationStorage;
private final Random mTokenGenerator = new Random();
@@ -547,6 +518,8 @@ public class UserBackupManagerService {
mRegisterTransportsRequestedTime = 0;
mPackageManager = packageManager;
mOperationStorage = operationStorage;
+ mBackupAgentConnectionManager = new BackupAgentConnectionManager(mOperationStorage,
+ mPackageManager, this, mUserId);
mTransportManager = transportManager;
mFullBackupQueue = new ArrayList<>();
mBackupHandler = backupHandler;
@@ -599,6 +572,8 @@ public class UserBackupManagerService {
mAgentTimeoutParameters.start();
mOperationStorage = new LifecycleOperationStorage(mUserId);
+ mBackupAgentConnectionManager = new BackupAgentConnectionManager(mOperationStorage,
+ mPackageManager, this, mUserId);
Objects.requireNonNull(userBackupThread, "userBackupThread cannot be null");
mBackupHandler = new BackupHandler(this, mOperationStorage, userBackupThread);
@@ -1660,67 +1635,6 @@ public class UserBackupManagerService {
}
}
- /** Fires off a backup agent, blocking until it attaches or times out. */
- @Nullable
- public IBackupAgent bindToAgentSynchronous(ApplicationInfo app, int mode,
- @BackupDestination int backupDestination) {
- IBackupAgent agent = null;
- synchronized (mAgentConnectLock) {
- mConnecting = true;
- mConnectedAgent = null;
- boolean useRestrictedMode = shouldUseRestrictedBackupModeForPackage(mode,
- app.packageName);
- try {
- if (mActivityManager.bindBackupAgent(app.packageName, mode, mUserId,
- backupDestination, useRestrictedMode)) {
- Slog.d(TAG, addUserIdToLogMessage(mUserId, "awaiting agent for " + app));
-
- // success; wait for the agent to arrive
- // only wait 10 seconds for the bind to happen
- long timeoutMark = System.currentTimeMillis() + BIND_TIMEOUT_INTERVAL;
- while (mConnecting && mConnectedAgent == null
- && (System.currentTimeMillis() < timeoutMark)) {
- try {
- mAgentConnectLock.wait(5000);
- } catch (InterruptedException e) {
- // just bail
- Slog.w(TAG, addUserIdToLogMessage(mUserId, "Interrupted: " + e));
- mConnecting = false;
- mConnectedAgent = null;
- }
- }
-
- // if we timed out with no connect, abort and move on
- if (mConnecting) {
- Slog.w(
- TAG,
- addUserIdToLogMessage(mUserId, "Timeout waiting for agent " + app));
- mConnectedAgent = null;
- }
- if (DEBUG) {
- Slog.i(TAG, addUserIdToLogMessage(mUserId, "got agent " + mConnectedAgent));
- }
- agent = mConnectedAgent;
- }
- } catch (RemoteException e) {
- // can't happen - ActivityManager is local
- }
- }
- if (agent == null) {
- mActivityManagerInternal.clearPendingBackup(mUserId);
- }
- return agent;
- }
-
- /** Unbind from a backup agent. */
- public void unbindAgent(ApplicationInfo app) {
- try {
- mActivityManager.unbindBackupAgent(app);
- } catch (RemoteException e) {
- // Can't happen - activity manager is local
- }
- }
-
/**
* Clear an application's data after a failed restore, blocking until the operation completes or
* times out.
@@ -2493,10 +2407,6 @@ public class UserBackupManagerService {
AppWidgetBackupBridge.restoreWidgetState(packageName, widgetData, mUserId);
}
- // *****************************
- // NEW UNIFIED RESTORE IMPLEMENTATION
- // *****************************
-
/** Schedule a backup pass for {@code packageName}. */
public void dataChangedImpl(String packageName) {
HashSet<String> targets = dataChangedTargets(packageName);
@@ -3122,91 +3032,6 @@ public class UserBackupManagerService {
}
}
- /**
- * Marks the given set of packages as packages that should not be put into restricted mode if
- * they are started for the given {@link BackupAnnotations.OperationType}.
- */
- public void setNoRestrictedModePackages(Set<String> packageNames,
- @BackupAnnotations.OperationType int opType) {
- if (opType == BackupAnnotations.OperationType.BACKUP) {
- mBackupNoRestrictedModePackages.clear();
- mBackupNoRestrictedModePackages.addAll(packageNames);
- } else if (opType == BackupAnnotations.OperationType.RESTORE) {
- mRestoreNoRestrictedModePackages.clear();
- mRestoreNoRestrictedModePackages.addAll(packageNames);
- } else {
- throw new IllegalArgumentException("opType must be BACKUP or RESTORE");
- }
- }
-
- /**
- * Clears the list of packages that should not be put into restricted mode for either backup or
- * restore.
- */
- public void clearNoRestrictedModePackages() {
- mBackupNoRestrictedModePackages.clear();
- mRestoreNoRestrictedModePackages.clear();
- }
-
- /**
- * If the app has specified {@link PackageManager#PROPERTY_USE_RESTRICTED_BACKUP_MODE}, then
- * its value is returned. If it hasn't and it targets an SDK below
- * {@link Build.VERSION_CODES#BAKLAVA} then returns true. If it targets a newer SDK, then
- * returns the decision made by the {@link android.app.backup.BackupTransport}.
- *
- * <p>When this method is called, we should have already asked the transport and cached its
- * response in {@link #mBackupNoRestrictedModePackages} or
- * {@link #mRestoreNoRestrictedModePackages} so this method will immediately return without
- * any IPC to the transport.
- */
- private boolean shouldUseRestrictedBackupModeForPackage(
- @BackupAnnotations.OperationType int mode, String packageName) {
- if (!Flags.enableRestrictedModeChanges()) {
- return true;
- }
-
- // Key/Value apps are never put in restricted mode.
- if (mode == ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL
- || mode == ApplicationThreadConstants.BACKUP_MODE_RESTORE) {
- return false;
- }
-
- try {
- PackageManager.Property property = mPackageManager.getPropertyAsUser(
- PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE,
- packageName, /* className= */ null,
- mUserId);
- if (property.isBoolean()) {
- // If the package has explicitly specified, we won't ask the transport.
- return property.getBoolean();
- } else {
- Slog.w(TAG, PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE
- + "must be a boolean.");
- }
- } catch (NameNotFoundException e) {
- // This is expected when the package has not defined the property in its manifest.
- }
-
- // The package has not specified the property. The behavior depends on the package's
- // targetSdk.
- // <36 gets the old behavior of always using restricted mode.
- if (!CompatChanges.isChangeEnabled(OS_DECIDES_BACKUP_RESTRICTED_MODE, packageName,
- UserHandle.of(mUserId))) {
- return true;
- }
-
- // Apps targeting >=36 get the behavior decided by the transport.
- // By this point, we should have asked the transport and cached its decision.
- if ((mode == ApplicationThreadConstants.BACKUP_MODE_FULL
- && mBackupNoRestrictedModePackages.contains(packageName))
- || (mode == ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL
- && mRestoreNoRestrictedModePackages.contains(packageName))) {
- Slog.d(TAG, "Transport requested no restricted mode for: " + packageName);
- return false;
- }
- return true;
- }
-
private boolean startConfirmationUi(int token, String action) {
try {
Intent confIntent = new Intent(action);
@@ -3898,83 +3723,6 @@ public class UserBackupManagerService {
}
/**
- * Callback: a requested backup agent has been instantiated. This should only be called from the
- * {@link ActivityManager}.
- */
- public void agentConnected(String packageName, IBinder agentBinder) {
- synchronized (mAgentConnectLock) {
- if (Binder.getCallingUid() == Process.SYSTEM_UID) {
- Slog.d(
- TAG,
- addUserIdToLogMessage(
- mUserId,
- "agentConnected pkg=" + packageName + " agent=" + agentBinder));
- mConnectedAgent = IBackupAgent.Stub.asInterface(agentBinder);
- mConnecting = false;
- } else {
- Slog.w(
- TAG,
- addUserIdToLogMessage(
- mUserId,
- "Non-system process uid="
- + Binder.getCallingUid()
- + " claiming agent connected"));
- }
- mAgentConnectLock.notifyAll();
- }
- }
-
- /**
- * Callback: a backup agent has failed to come up, or has unexpectedly quit. If the agent failed
- * to come up in the first place, the agentBinder argument will be {@code null}. This should
- * only be called from the {@link ActivityManager}.
- */
- public void agentDisconnected(String packageName) {
- synchronized (mAgentConnectLock) {
- if (Binder.getCallingUid() == Process.SYSTEM_UID) {
- mConnectedAgent = null;
- mConnecting = false;
- } else {
- Slog.w(
- TAG,
- addUserIdToLogMessage(
- mUserId,
- "Non-system process uid="
- + Binder.getCallingUid()
- + " claiming agent disconnected"));
- }
- Slog.w(TAG, "agentDisconnected: the backup agent for " + packageName
- + " died: cancel current operations");
-
- // Offload operation cancellation off the main thread as the cancellation callbacks
- // might call out to BackupTransport. Other operations started on the same package
- // before the cancellation callback has executed will also be cancelled by the callback.
- Runnable cancellationRunnable = () -> {
- // handleCancel() causes the PerformFullTransportBackupTask to go on to
- // tearDownAgentAndKill: that will unbindBackupAgent in the Activity Manager, so
- // that the package being backed up doesn't get stuck in restricted mode until the
- // backup time-out elapses.
- for (int token : mOperationStorage.operationTokensForPackage(packageName)) {
- if (MORE_DEBUG) {
- Slog.d(TAG, "agentDisconnected: will handleCancel(all) for token:"
- + Integer.toHexString(token));
- }
- handleCancel(token, true /* cancelAll */);
- }
- };
- getThreadForAsyncOperation(/* operationName */ "agent-disconnected",
- cancellationRunnable).start();
-
- mAgentConnectLock.notifyAll();
- }
- }
-
- @VisibleForTesting
- Thread getThreadForAsyncOperation(String operationName, Runnable operation) {
- return new Thread(operation, operationName);
- }
-
- /**
* An application being installed will need a restore pass, then the {@link PackageManager} will
* need to be told when the restore is finished.
*/
@@ -4521,4 +4269,8 @@ public class UserBackupManagerService {
public IBackupManager getBackupManagerBinder() {
return mBackupManagerBinder;
}
+
+ public BackupAgentConnectionManager getBackupAgentConnectionManager() {
+ return mBackupAgentConnectionManager;
+ }
}
diff --git a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
index 12712063e344..b98cb1086680 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
@@ -314,7 +314,7 @@ public class FullBackupEngine {
Slog.d(TAG, "Binding to full backup agent : " + mPkg.packageName);
}
mAgent =
- backupManagerService.bindToAgentSynchronous(
+ backupManagerService.getBackupAgentConnectionManager().bindToAgentSynchronous(
mPkg.applicationInfo, ApplicationThreadConstants.BACKUP_MODE_FULL,
mBackupEligibilityRules.getBackupDestination());
}
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index be9cdc8692cb..65730c9591a8 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -702,7 +702,8 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
}
// Clear this to avoid using the memory until reboot.
- mUserBackupManagerService.clearNoRestrictedModePackages();
+ mUserBackupManagerService
+ .getBackupAgentConnectionManager().clearNoRestrictedModePackages();
Slog.i(TAG, "Full data backup pass finished.");
mUserBackupManagerService.getWakelock().release();
@@ -741,7 +742,8 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
}
packageNames = transport.getPackagesThatShouldNotUseRestrictedMode(packageNames,
BACKUP);
- mUserBackupManagerService.setNoRestrictedModePackages(packageNames, BACKUP);
+ mUserBackupManagerService.getBackupAgentConnectionManager().setNoRestrictedModePackages(
+ packageNames, BACKUP);
} catch (RemoteException e) {
Slog.i(TAG, "Failed to retrieve no restricted mode packages from transport");
}
diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
index 3a6e1cafa505..82232a653858 100644
--- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
+++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
@@ -741,7 +741,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
final IBackupAgent agent;
try {
agent =
- mBackupManagerService.bindToAgentSynchronous(
+ mBackupManagerService.getBackupAgentConnectionManager().bindToAgentSynchronous(
packageInfo.applicationInfo, BACKUP_MODE_INCREMENTAL,
mBackupEligibilityRules.getBackupDestination());
if (agent == null) {
@@ -1302,7 +1302,8 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
// For PM metadata (for which applicationInfo is null) there is no agent-bound state.
if (mCurrentPackage.applicationInfo != null) {
- mBackupManagerService.unbindAgent(mCurrentPackage.applicationInfo);
+ mBackupManagerService.getBackupAgentConnectionManager().unbindAgent(
+ mCurrentPackage.applicationInfo);
}
mAgent = null;
}
diff --git a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
index 2d99c96452da..b59e860f81fe 100644
--- a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
+++ b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
@@ -410,11 +410,7 @@ public class FullRestoreEngine extends RestoreEngine {
// All set; now set up the IPC and launch the agent
setUpPipes();
- mAgent = mBackupManagerService.bindToAgentSynchronous(mTargetApp,
- FullBackup.KEY_VALUE_DATA_TOKEN.equals(info.domain)
- ? ApplicationThreadConstants.BACKUP_MODE_RESTORE
- : ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL,
- mBackupEligibilityRules.getBackupDestination());
+ mAgent = bindToAgent(info);
mAgentPackage = pkg;
} catch (IOException | NameNotFoundException e) {
// fall through to error handling
@@ -805,15 +801,12 @@ public class FullRestoreEngine extends RestoreEngine {
return packages.contains(packageName);
}
- void sendOnRestorePackage(String name) {
- if (mObserver != null) {
- try {
- // TODO: use a more user-friendly name string
- mObserver.onRestorePackage(name);
- } catch (RemoteException e) {
- Slog.w(TAG, "full restore observer went away: restorePackage");
- mObserver = null;
- }
- }
+ private IBackupAgent bindToAgent(FileMetadata info) {
+ return mBackupManagerService.getBackupAgentConnectionManager().bindToAgentSynchronous(
+ mTargetApp,
+ FullBackup.KEY_VALUE_DATA_TOKEN.equals(info.domain)
+ ? ApplicationThreadConstants.BACKUP_MODE_RESTORE
+ : ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL,
+ mBackupEligibilityRules.getBackupDestination());
}
}
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index 5ee51a5aa189..e5c7e5cce757 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -814,7 +814,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
// Good to go! Set up and bind the agent...
mAgent =
- backupManagerService.bindToAgentSynchronous(
+ backupManagerService.getBackupAgentConnectionManager().bindToAgentSynchronous(
mCurrentPackage.applicationInfo,
ApplicationThreadConstants.BACKUP_MODE_RESTORE,
mBackupEligibilityRules.getBackupDestination());
@@ -1364,7 +1364,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
backupManagerService.getBackupHandler().removeMessages(MSG_RESTORE_SESSION_TIMEOUT);
// Clear this to avoid using the memory until reboot.
- backupManagerService.clearNoRestrictedModePackages();
+ backupManagerService.getBackupAgentConnectionManager().clearNoRestrictedModePackages();
// If we have a PM token, we must under all circumstances be sure to
// handshake when we've finished.
@@ -1838,7 +1838,8 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
}
packageNames = transport.getPackagesThatShouldNotUseRestrictedMode(packageNames,
RESTORE);
- backupManagerService.setNoRestrictedModePackages(packageNames, RESTORE);
+ backupManagerService.getBackupAgentConnectionManager().setNoRestrictedModePackages(
+ packageNames, RESTORE);
} catch (RemoteException e) {
Slog.i(TAG, "Failed to retrieve restricted mode packages from transport");
}
diff --git a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
index dbeca82ade89..2d3782fb3181 100644
--- a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
+++ b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
@@ -16,6 +16,8 @@
package com.android.server.companion.securechannel;
+import static android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_UNKNOWN;
+
import android.annotation.NonNull;
import android.content.Context;
import android.os.Build;
@@ -67,7 +69,7 @@ public class SecureChannel {
private D2DConnectionContextV1 mConnectionContext;
private String mAlias;
- private int mVerificationResult;
+ private int mVerificationResult = FLAG_FAILURE_UNKNOWN;
private boolean mPskVerified;
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 679c7ac3ceac..3ce645158fe4 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -16,6 +16,10 @@
package com.android.server.accounts;
+import static android.Manifest.permission.COPY_ACCOUNTS;
+import static android.Manifest.permission.REMOVE_ACCOUNTS;
+import static android.app.admin.flags.Flags.splitCreateManagedProfileEnabled;
+
import android.Manifest;
import android.accounts.AbstractAccountAuthenticator;
import android.accounts.Account;
@@ -1739,9 +1743,11 @@ public class AccountManagerService
public void copyAccountToUser(final IAccountManagerResponse response, final Account account,
final int userFrom, int userTo) {
int callingUid = Binder.getCallingUid();
- if (isCrossUser(callingUid, UserHandle.USER_ALL)) {
+ if (isCrossUser(callingUid, UserHandle.USER_ALL)
+ && !hasCopyAccountsPermission()) {
throw new SecurityException("Calling copyAccountToUser requires "
- + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+ + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL
+ + " or " + COPY_ACCOUNTS);
}
final UserAccounts fromAccounts = getUserAccounts(userFrom);
final UserAccounts toAccounts = getUserAccounts(userTo);
@@ -1793,6 +1799,12 @@ public class AccountManagerService
}
}
+ private boolean hasCopyAccountsPermission() {
+ return splitCreateManagedProfileEnabled()
+ && mContext.checkCallingOrSelfPermission(COPY_ACCOUNTS)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
@Override
public boolean accountAuthenticated(final Account account) {
final int callingUid = Binder.getCallingUid();
@@ -2346,7 +2358,8 @@ public class AccountManagerService
UserHandle user = UserHandle.of(userId);
if (!isAccountManagedByCaller(account.type, callingUid, user.getIdentifier())
&& !isSystemUid(callingUid)
- && !isProfileOwner(callingUid)) {
+ && !isProfileOwner(callingUid)
+ && !hasRemoveAccountsPermission()) {
String msg = String.format(
"uid %s cannot remove accounts of type: %s",
callingUid,
@@ -2408,6 +2421,12 @@ public class AccountManagerService
}
}
+ private boolean hasRemoveAccountsPermission() {
+ return splitCreateManagedProfileEnabled()
+ && mContext.checkCallingOrSelfPermission(REMOVE_ACCOUNTS)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
@Override
public boolean removeAccountExplicitly(Account account) {
final int callingUid = Binder.getCallingUid();
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d880bce921aa..e166807083ff 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -19070,8 +19070,13 @@ public class ActivityManagerService extends IActivityManager.Stub
*/
@Override
public boolean enableFgsNotificationRateLimit(boolean enable) {
- enforceCallingPermission(permission.WRITE_DEVICE_CONFIG,
- "enableFgsNotificationRateLimit");
+ if (android.security.Flags.protectDeviceConfigFlags()) {
+ enforceCallingHasAtLeastOnePermission("enableFgsNotificationRateLimit",
+ permission.WRITE_DEVICE_CONFIG, permission.WRITE_ALLOWLISTED_DEVICE_CONFIG);
+ } else {
+ enforceCallingPermission(permission.WRITE_DEVICE_CONFIG,
+ "enableFgsNotificationRateLimit");
+ }
synchronized (this) {
return mServices.enableFgsNotificationRateLimitLocked(enable);
}
diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
index 3913d2f2ea92..961022b7231b 100644
--- a/services/core/java/com/android/server/am/AppStartInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -634,12 +634,20 @@ public final class AppStartInfoTracker {
}
final ApplicationStartInfo info = new ApplicationStartInfo(raw);
+ int uid = raw.getRealUid();
- AppStartInfoContainer container = mData.get(raw.getPackageName(), raw.getRealUid());
+ // Isolated process starts won't be reasonably accessible if stored by their uid, don't
+ // store them.
+ if (com.android.server.am.Flags.appStartInfoIsolatedProcess()
+ && UserHandle.isIsolated(uid)) {
+ return null;
+ }
+
+ AppStartInfoContainer container = mData.get(raw.getPackageName(), uid);
if (container == null) {
container = new AppStartInfoContainer(mAppStartInfoHistoryListSize);
- container.mUid = info.getRealUid();
- mData.put(raw.getPackageName(), raw.getRealUid(), container);
+ container.mUid = uid;
+ mData.put(raw.getPackageName(), uid, container);
}
container.addStartInfoLocked(info);
@@ -1010,6 +1018,17 @@ public final class AppStartInfoTracker {
new AppStartInfoContainer(mAppStartInfoHistoryListSize);
int uid = container.readFromProto(proto, AppsStartInfoProto.Package.USERS,
pkgName);
+
+ // If the isolated process flag is enabled and the uid is that of an isolated
+ // process, then break early so that the container will not be added to mData.
+ // This is expected only as a one time mitigation, records added after this flag
+ // is enabled should always return false for isIsolated and thereby always
+ // continue on.
+ if (com.android.server.am.Flags.appStartInfoIsolatedProcess()
+ && UserHandle.isIsolated(uid)) {
+ break;
+ }
+
synchronized (mLock) {
mData.put(pkgName, uid, container);
}
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index ef5296eef492..78c4f74f3afa 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -257,6 +257,7 @@ public class SettingsToPropertiesMapper {
"wear_systems",
"wear_sysui",
"wear_system_managed_surfaces",
+ "wear_watchfaces",
"window_surfaces",
"windowing_frontend",
"xr",
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index c59c40fc9cd8..6d247d227774 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -270,3 +270,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "app_start_info_isolated_process"
+ namespace: "system_performance"
+ description: "Adjust handling of isolated process records to be discarded."
+ bug: "374032823"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index 82d5d4d8141d..e8786be4d8e6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -677,10 +677,11 @@ public class BiometricScheduler<T, U> {
* Start the timeout for the watchdog.
*/
public void startWatchdog() {
- if (mCurrentOperation == null) {
+ final BiometricSchedulerOperation operation = mCurrentOperation;
+ if (operation == null) {
+ Slog.e(TAG, "Current operation is null,no need to start watchdog");
return;
}
- final BiometricSchedulerOperation operation = mCurrentOperation;
mHandler.postDelayed(() -> {
if (operation == mCurrentOperation && !operation.isFinished()) {
Counter.logIncrement("biometric.value_scheduler_watchdog_triggered_count");
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index edad2473061c..e5b077d23bec 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -3031,6 +3031,11 @@ public class InputManagerService extends IInputManager.Stub
return mKeyGestureController.getAppLaunchBookmarks();
}
+ @Override
+ public void resetLockedModifierState() {
+ mNative.resetLockedModifierState();
+ }
+
private void handleCurrentUserChanged(@UserIdInt int userId) {
mCurrentUserId = userId;
mKeyGestureController.setCurrentUserId(userId);
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 8903c27b491a..728e44062e82 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -98,6 +98,8 @@ interface NativeInputManagerService {
void toggleCapsLock(int deviceId);
+ void resetLockedModifierState();
+
void displayRemoved(int displayId);
void setInputDispatchMode(boolean enabled, boolean frozen);
@@ -370,6 +372,9 @@ interface NativeInputManagerService {
public native void toggleCapsLock(int deviceId);
@Override
+ public native void resetLockedModifierState();
+
+ @Override
public native void displayRemoved(int displayId);
@Override
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index d8483f721306..b7af9a4b17bd 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1310,7 +1310,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
// Do not reset the default (current) IME when it is a 3rd-party IME
String selectedMethodId = bindingController.getSelectedMethodId();
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
- if (selectedMethodId != null
+ if (selectedMethodId != null && settings.getMethodMap().get(selectedMethodId) != null
&& !settings.getMethodMap().get(selectedMethodId).isSystem()) {
return;
}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java
index 0fdd0ae641df..5248a051404d 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientManager.java
@@ -237,15 +237,16 @@ import java.util.function.Consumer;
if (message.isBroadcastMessage()) {
if (message.isReliable()) {
- Log.e(TAG, "Received reliable broadcast message from " + message.getNanoAppId());
+ Log.e(TAG, "Received reliable broadcast message from 0x"
+ + Long.toHexString(message.getNanoAppId()));
return ErrorCode.PERMANENT_ERROR;
}
// Broadcast messages shouldn't be sent with any permissions tagged per CHRE API
// requirements.
if (!messagePermissions.isEmpty()) {
- Log.e(TAG, "Received broadcast message with permissions from "
- + message.getNanoAppId());
+ Log.e(TAG, "Received broadcast message with permissions from 0x"
+ + Long.toHexString(message.getNanoAppId()));
return ErrorCode.PERMANENT_ERROR;
}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 946e89604553..7c9d9c57a8b0 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -326,8 +326,15 @@ public class ContextHubService extends IContextHubService.Stub {
}
if (Flags.offloadApi()) {
- mHubInfoRegistry = new HubInfoRegistry(mContextHubWrapper);
- Log.i(TAG, "Enabling generic offload API");
+ HubInfoRegistry registry;
+ try {
+ registry = new HubInfoRegistry(mContextHubWrapper);
+ Log.i(TAG, "Enabling generic offload API");
+ } catch (UnsupportedOperationException e) {
+ registry = null;
+ Log.w(TAG, "Generic offload API not supported, disabling");
+ }
+ mHubInfoRegistry = registry;
} else {
mHubInfoRegistry = null;
Log.i(TAG, "Disabling generic offload API");
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 fce008c23350..df5ecf872df4 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -1006,9 +1006,9 @@ public final class MediaProjectionManagerService extends SystemService
// Host app has 5 minutes to begin using the token before it is invalid.
// Some apps show a dialog for the user to interact with (selecting recording resolution)
// before starting capture, but after requesting consent.
- final long mDefaultTimeoutMs = Duration.ofMinutes(5).toMillis();
+ final long mDefaultTimeoutMillis = Duration.ofMinutes(5).toMillis();
// The creation timestamp in milliseconds, measured by {@link SystemClock#uptimeMillis}.
- private final long mCreateTimeMs;
+ private final long mCreateTimeMillis;
public final int uid;
public final String packageName;
public final UserHandle userHandle;
@@ -1017,7 +1017,7 @@ public final class MediaProjectionManagerService extends SystemService
private final int mType;
// Values for tracking token validity.
// Timeout value to compare creation time against.
- private final long mTimeoutMs = mDefaultTimeoutMs;
+ private final long mTimeoutMillis = mDefaultTimeoutMillis;
private final int mDisplayId;
private IMediaProjectionCallback mCallback;
@@ -1048,7 +1048,7 @@ public final class MediaProjectionManagerService extends SystemService
userHandle = new UserHandle(UserHandle.getUserId(uid));
mTargetSdkVersion = targetSdkVersion;
mIsPrivileged = isPrivileged;
- mCreateTimeMs = mClock.uptimeMillis();
+ mCreateTimeMillis = mClock.uptimeMillis();
mActivityManagerInternal.notifyMediaProjectionEvent(uid, asBinder(),
MEDIA_PROJECTION_TOKEN_EVENT_CREATED);
mDisplayId = displayId;
@@ -1209,7 +1209,7 @@ public final class MediaProjectionManagerService extends SystemService
}
}
Slog.d(TAG, "Content Recording: handling stopping this projection token"
- + " createTime= " + mCreateTimeMs
+ + " createTime= " + mCreateTimeMillis
+ " countStarts= " + mCountStarts);
stopProjectionLocked(this);
mToken.unlinkToDeath(mDeathEater, 0);
@@ -1273,7 +1273,7 @@ public final class MediaProjectionManagerService extends SystemService
}
long getCreateTimeMillis() {
- return mCreateTimeMs;
+ return mCreateTimeMillis;
}
@android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION)
@@ -1281,8 +1281,8 @@ public final class MediaProjectionManagerService extends SystemService
public boolean isValid() {
isValid_enforcePermission();
synchronized (mLock) {
- final long curMs = mClock.uptimeMillis();
- final boolean hasTimedOut = curMs - mCreateTimeMs > mTimeoutMs;
+ final long curMillis = mClock.uptimeMillis();
+ final boolean hasTimedOut = curMillis - mCreateTimeMillis > mTimeoutMillis;
final boolean virtualDisplayCreated = mVirtualDisplayId != INVALID_DISPLAY;
final boolean isValid =
!hasTimedOut && (mCountStarts <= 1) && !virtualDisplayCreated;
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java
index 00bab8af44f3..8495b6c403bf 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -28,6 +28,7 @@ import android.media.quality.ISoundProfileCallback;
import android.media.quality.MediaQualityContract;
import android.media.quality.ParamCapability;
import android.media.quality.PictureProfile;
+import android.media.quality.PictureProfileHandle;
import android.media.quality.SoundProfile;
import android.os.PersistableBundle;
import android.util.Log;
@@ -249,6 +250,11 @@ public class MediaQualityService extends SystemService {
}
@Override
+ public PictureProfileHandle getPictureProfileHandle(String id) {
+ return null;
+ }
+
+ @Override
public SoundProfile createSoundProfile(SoundProfile pp) {
// TODO: implement
return pp;
diff --git a/services/core/java/com/android/server/security/authenticationpolicy/AuthenticationPolicyService.java b/services/core/java/com/android/server/security/authenticationpolicy/AuthenticationPolicyService.java
index b8a4a9c26feb..6798a6146ae0 100644
--- a/services/core/java/com/android/server/security/authenticationpolicy/AuthenticationPolicyService.java
+++ b/services/core/java/com/android/server/security/authenticationpolicy/AuthenticationPolicyService.java
@@ -16,8 +16,11 @@
package com.android.server.security.authenticationpolicy;
+import static android.Manifest.permission.MANAGE_SECURE_LOCK_DEVICE;
+
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST;
+import android.annotation.EnforcePermission;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -32,9 +35,14 @@ import android.hardware.biometrics.events.AuthenticationStoppedInfo;
import android.hardware.biometrics.events.AuthenticationSucceededInfo;
import android.os.Build;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
+import android.security.authenticationpolicy.AuthenticationPolicyManager;
+import android.security.authenticationpolicy.DisableSecureLockDeviceParams;
+import android.security.authenticationpolicy.EnableSecureLockDeviceParams;
+import android.security.authenticationpolicy.IAuthenticationPolicyService;
import android.util.Log;
import android.util.Slog;
import android.util.SparseIntArray;
@@ -74,6 +82,7 @@ public class AuthenticationPolicyService extends SystemService {
private final KeyguardManager mKeyguardManager;
private final WindowManagerInternal mWindowManager;
private final UserManagerInternal mUserManager;
+ private SecureLockDeviceServiceInternal mSecureLockDeviceService;
@VisibleForTesting
final SparseIntArray mFailedAttemptsForUser = new SparseIntArray();
private final SparseLongArray mLastLockedTimestamp = new SparseLongArray();
@@ -94,10 +103,16 @@ public class AuthenticationPolicyService extends SystemService {
mWindowManager = Objects.requireNonNull(
LocalServices.getService(WindowManagerInternal.class));
mUserManager = Objects.requireNonNull(LocalServices.getService(UserManagerInternal.class));
+ if (android.security.Flags.secureLockdown()) {
+ mSecureLockDeviceService = Objects.requireNonNull(
+ LocalServices.getService(SecureLockDeviceServiceInternal.class));
+ }
}
@Override
- public void onStart() {}
+ public void onStart() {
+ publishBinderService(Context.AUTHENTICATION_POLICY_SERVICE, mService);
+ }
@Override
public void onBootPhase(int phase) {
@@ -294,4 +309,36 @@ public class AuthenticationPolicyService extends SystemService {
// next successful primary or biometric auth happens
mLastLockedTimestamp.put(userId, SystemClock.elapsedRealtime());
}
+
+ private final IBinder mService = new IAuthenticationPolicyService.Stub() {
+ /**
+ * @see AuthenticationPolicyManager#enableSecureLockDevice(EnableSecureLockDeviceParams)
+ * @param params EnableSecureLockDeviceParams for caller to supply params related
+ * to the secure lock device request
+ * @return @EnableSecureLockDeviceRequestStatus int indicating the result of the Secure
+ * Lock Device request
+ */
+ @Override
+ @EnforcePermission(MANAGE_SECURE_LOCK_DEVICE)
+ @AuthenticationPolicyManager.EnableSecureLockDeviceRequestStatus
+ public int enableSecureLockDevice(EnableSecureLockDeviceParams params) {
+ enableSecureLockDevice_enforcePermission();
+ return mSecureLockDeviceService.enableSecureLockDevice(params);
+ }
+
+ /**
+ * @see AuthenticationPolicyManager#disableSecureLockDevice(DisableSecureLockDeviceParams)
+ * @param params @DisableSecureLockDeviceParams for caller to supply params related
+ * to the secure lock device request
+ * @return @DisableSecureLockDeviceRequestStatus int indicating the result of the Secure
+ * Lock Device request
+ */
+ @Override
+ @EnforcePermission(MANAGE_SECURE_LOCK_DEVICE)
+ @AuthenticationPolicyManager.DisableSecureLockDeviceRequestStatus
+ public int disableSecureLockDevice(DisableSecureLockDeviceParams params) {
+ disableSecureLockDevice_enforcePermission();
+ return mSecureLockDeviceService.disableSecureLockDevice(params);
+ }
+ };
}
diff --git a/services/core/java/com/android/server/security/authenticationpolicy/SecureLockDeviceService.java b/services/core/java/com/android/server/security/authenticationpolicy/SecureLockDeviceService.java
new file mode 100644
index 000000000000..7b89723deb6c
--- /dev/null
+++ b/services/core/java/com/android/server/security/authenticationpolicy/SecureLockDeviceService.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2024 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.security.authenticationpolicy;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.security.authenticationpolicy.AuthenticationPolicyManager;
+import android.security.authenticationpolicy.AuthenticationPolicyManager.DisableSecureLockDeviceRequestStatus;
+import android.security.authenticationpolicy.AuthenticationPolicyManager.EnableSecureLockDeviceRequestStatus;
+import android.security.authenticationpolicy.DisableSecureLockDeviceParams;
+import android.security.authenticationpolicy.EnableSecureLockDeviceParams;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+
+/**
+ * System service for remotely calling secure lock on the device.
+ *
+ * Callers will access this class via
+ * {@link com.android.server.security.authenticationpolicy.AuthenticationPolicyService}.
+ *
+ * @see AuthenticationPolicyService
+ * @see AuthenticationPolicyManager#enableSecureLockDevice
+ * @see AuthenticationPolicyManager#disableSecureLockDevice
+ * @hide
+ */
+public class SecureLockDeviceService extends SecureLockDeviceServiceInternal {
+ private static final String TAG = "SecureLockDeviceService";
+ private final Context mContext;
+
+ public SecureLockDeviceService(@NonNull Context context) {
+ mContext = context;
+ }
+
+ private void start() {
+ // Expose private service for system components to use.
+ LocalServices.addService(SecureLockDeviceServiceInternal.class, this);
+ }
+
+ /**
+ * @see AuthenticationPolicyManager#enableSecureLockDevice
+ * @param params EnableSecureLockDeviceParams for caller to supply params related
+ * to the secure lock device request
+ * @return @EnableSecureLockDeviceRequestStatus int indicating the result of the
+ * secure lock device request
+ *
+ * @hide
+ */
+ @Override
+ @EnableSecureLockDeviceRequestStatus
+ public int enableSecureLockDevice(EnableSecureLockDeviceParams params) {
+ // (1) Call into system_server to lock device, configure allowed auth types
+ // for secure lock
+ // TODO: lock device, configure allowed authentication types for device entry
+ // (2) Call into framework to configure secure lock 2FA lockscreen
+ // update, UI & string updates
+ // TODO: implement 2FA lockscreen when SceneContainerFlag.isEnabled()
+ // TODO: implement 2FA lockscreen when !SceneContainerFlag.isEnabled()
+ // (3) Call into framework to configure keyguard security updates
+ // TODO: implement security updates
+ return AuthenticationPolicyManager.ERROR_UNSUPPORTED;
+ }
+
+ /**
+ * @see AuthenticationPolicyManager#disableSecureLockDevice
+ * @param params @DisableSecureLockDeviceParams for caller to supply params related
+ * to the secure lock device request
+ * @return @DisableSecureLockDeviceRequestStatus int indicating the result of the
+ * secure lock device request
+ *
+ * @hide
+ */
+ @Override
+ @DisableSecureLockDeviceRequestStatus
+ public int disableSecureLockDevice(DisableSecureLockDeviceParams params) {
+ // (1) Call into system_server to reset allowed auth types
+ // TODO: reset allowed authentication types for device entry;
+ // (2) Call into framework to disable secure lock 2FA lockscreen, reset UI
+ // & string updates
+ // TODO: implement reverting to normal lockscreen when SceneContainerFlag.isEnabled()
+ // TODO: implement reverting to normal lockscreen when !SceneContainerFlag.isEnabled()
+ // (3) Call into framework to revert keyguard security updates
+ // TODO: implement reverting security updates
+ return AuthenticationPolicyManager.ERROR_UNSUPPORTED;
+ }
+
+ /**
+ * System service lifecycle.
+ */
+ public static final class Lifecycle extends SystemService {
+ private final SecureLockDeviceService mService;
+
+ public Lifecycle(@NonNull Context context) {
+ super(context);
+ mService = new SecureLockDeviceService(context);
+ }
+
+ @Override
+ public void onStart() {
+ Slog.i(TAG, "Starting SecureLockDeviceService");
+ mService.start();
+ Slog.i(TAG, "Started SecureLockDeviceService");
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/security/authenticationpolicy/SecureLockDeviceServiceInternal.java b/services/core/java/com/android/server/security/authenticationpolicy/SecureLockDeviceServiceInternal.java
new file mode 100644
index 000000000000..b90370956d8b
--- /dev/null
+++ b/services/core/java/com/android/server/security/authenticationpolicy/SecureLockDeviceServiceInternal.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 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.security.authenticationpolicy;
+
+import android.security.authenticationpolicy.AuthenticationPolicyManager;
+import android.security.authenticationpolicy.AuthenticationPolicyManager.DisableSecureLockDeviceRequestStatus;
+import android.security.authenticationpolicy.AuthenticationPolicyManager.EnableSecureLockDeviceRequestStatus;
+import android.security.authenticationpolicy.DisableSecureLockDeviceParams;
+import android.security.authenticationpolicy.EnableSecureLockDeviceParams;
+
+/**
+ * Local system service interface for {@link SecureLockDeviceService}.
+ *
+ * <p>No permission / argument checks will be performed inside.
+ * Callers must check the calling app permission and the calling package name.
+ *
+ * @hide
+ */
+public abstract class SecureLockDeviceServiceInternal {
+ private static final String TAG = "SecureLockDeviceServiceInternal";
+
+ /**
+ * @see AuthenticationPolicyManager#enableSecureLockDevice(EnableSecureLockDeviceParams)
+ * @param params EnableSecureLockDeviceParams for caller to supply params related
+ * to the secure lock request
+ * @return @EnableSecureLockDeviceRequestStatus int indicating the result of the
+ * secure lock request
+ */
+ @EnableSecureLockDeviceRequestStatus
+ public abstract int enableSecureLockDevice(EnableSecureLockDeviceParams params);
+
+ /**
+ * @see AuthenticationPolicyManager#disableSecureLockDevice(DisableSecureLockDeviceParams)
+ * @param params @DisableSecureLockDeviceParams for caller to supply params related
+ * to the secure lock device request
+ * @return @DisableSecureLockDeviceRequestStatus int indicating the result of the
+ * secure lock device request
+ */
+ @DisableSecureLockDeviceRequestStatus
+ public abstract int disableSecureLockDevice(DisableSecureLockDeviceParams params);
+}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index c6e6e761c0bc..f70dec175c06 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -630,8 +630,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// The locusId associated with this activity, if set.
private LocusId mLocusId;
- // Whether the activity is requesting to limit the system's educational dialogs
- public boolean mShouldLimitSystemEducationDialogs;
+ // The timestamp of the last request to show the "Open in browser" education
+ public long mRequestOpenInBrowserEducationTimestamp;
// Whether the activity was launched from a bubble.
private boolean mLaunchedFromBubble;
@@ -1623,6 +1623,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
newParent.setResumedActivity(this, "onParentChanged");
}
mAppCompatController.getTransparentPolicy().start();
+ if (mState == INITIALIZING && isRestrictedFixedOrientation(info.screenOrientation)) {
+ Slog.i(TAG, "Ignoring manifest-declared fixed orientation "
+ + ActivityInfo.screenOrientationToString(info.screenOrientation)
+ + " of " + this + " since target sdk 36");
+ }
}
if (rootTask != null && rootTask.topRunningActivity() == this) {
@@ -3192,6 +3197,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
/**
+ * Returns {@code true} if the orientation will be ignored for {@link #isUniversalResizeable()}.
+ */
+ private boolean isRestrictedFixedOrientation(
+ @ActivityInfo.ScreenOrientation int orientation) {
+ // Exclude "locked" because it is not explicit portrait or landscape.
+ return orientation != ActivityInfo.SCREEN_ORIENTATION_LOCKED
+ && ActivityInfo.isFixedOrientation(orientation)
+ && isUniversalResizeable();
+ }
+
+ /**
* Returns {@code true} if the fixed orientation, aspect ratio, resizability of this activity
* will be ignored.
*/
@@ -7326,9 +7342,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return mLocusId;
}
- void setLimitSystemEducationDialogs(boolean limitSystemEducationDialogs) {
- if (mShouldLimitSystemEducationDialogs == limitSystemEducationDialogs) return;
- mShouldLimitSystemEducationDialogs = limitSystemEducationDialogs;
+ void requestOpenInBrowserEducation() {
+ mRequestOpenInBrowserEducationTimestamp = System.currentTimeMillis();
final Task task = getTask();
if (task != null) {
final boolean force = isVisibleRequested() && this == task.getTopNonFinishingActivity();
@@ -8123,7 +8138,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Setting requested orientation %s for %s",
ActivityInfo.screenOrientationToString(requestedOrientation), this);
- setOrientation(requestedOrientation, this);
+ final int resolvedOrientation = setOrientation(requestedOrientation, this);
+ if (resolvedOrientation != requestedOrientation
+ && isRestrictedFixedOrientation(requestedOrientation)) {
+ Slog.i(TAG, "Ignoring requested fixed orientation "
+ + ActivityInfo.screenOrientationToString(requestedOrientation)
+ + " of " + this + " since target sdk 36");
+ }
// Push the new configuration to the requested app in case where it's not pushed, e.g. when
// the request is handled at task level with letterbox.
@@ -8214,9 +8235,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
@ActivityInfo.ScreenOrientation
protected int getOverrideOrientation() {
int candidateOrientation = super.getOverrideOrientation();
- if (candidateOrientation != ActivityInfo.SCREEN_ORIENTATION_LOCKED
- && ActivityInfo.isFixedOrientation(candidateOrientation)
- && isUniversalResizeable()) {
+ if (isRestrictedFixedOrientation(candidateOrientation)) {
candidateOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
}
return mAppCompatController.getOrientationPolicy()
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 198e14a16500..8ff08187c698 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3915,12 +3915,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
@Override
- public void setLimitSystemEducationDialogs(
- IBinder appToken, boolean limitSystemEducationDialogs) {
+ public void requestOpenInBrowserEducation(IBinder appToken) {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(appToken);
if (r != null) {
- r.setLimitSystemEducationDialogs(limitSystemEducationDialogs);
+ r.requestOpenInBrowserEducation();
}
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 4857b02eaf7c..70a8f563275f 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -344,6 +344,11 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
private ActivityRecord mTopResumedActivity;
/**
+ * Cached value of the topmost resumed activity that reported to the client.
+ */
+ private ActivityRecord mLastReportedTopResumedActivity;
+
+ /**
* Flag indicating whether we're currently waiting for the previous top activity to handle the
* loss of the state and report back before making new activity top resumed.
*/
@@ -2287,15 +2292,13 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
* sent to the new top resumed activity.
*/
ActivityRecord updateTopResumedActivityIfNeeded(String reason) {
- if (!readyToResume()) {
- return mTopResumedActivity;
- }
final ActivityRecord prevTopActivity = mTopResumedActivity;
final Task topRootTask = mRootWindowContainer.getTopDisplayFocusedRootTask();
if (topRootTask == null || topRootTask.getTopResumedActivity() == prevTopActivity) {
if (topRootTask == null) {
// There's no focused task and there won't have any resumed activity either.
scheduleTopResumedActivityStateLossIfNeeded();
+ mTopResumedActivity = null;
}
if (mService.isSleepingLocked()) {
// There won't be a next resumed activity. The top process should still be updated
@@ -2339,25 +2342,27 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
/** Schedule current top resumed activity state loss */
private void scheduleTopResumedActivityStateLossIfNeeded() {
- if (mTopResumedActivity == null) {
+ if (mLastReportedTopResumedActivity == null) {
return;
}
// mTopResumedActivityWaitingForPrev == true at this point would mean that an activity
// before the prevTopActivity one hasn't reported back yet. So server never sent the top
// resumed state change message to prevTopActivity.
- if (!mTopResumedActivityWaitingForPrev
- && mTopResumedActivity.scheduleTopResumedActivityChanged(false /* onTop */)) {
- scheduleTopResumedStateLossTimeout(mTopResumedActivity);
+ if (!mTopResumedActivityWaitingForPrev && readyToResume()
+ && mLastReportedTopResumedActivity.scheduleTopResumedActivityChanged(
+ false /* onTop */)) {
+ scheduleTopResumedStateLossTimeout(mLastReportedTopResumedActivity);
mTopResumedActivityWaitingForPrev = true;
+ mLastReportedTopResumedActivity = null;
}
- mTopResumedActivity = null;
}
/** Schedule top resumed state change if previous top activity already reported back. */
private void scheduleTopResumedActivityStateIfNeeded() {
- if (mTopResumedActivity != null && !mTopResumedActivityWaitingForPrev) {
+ if (mTopResumedActivity != null && !mTopResumedActivityWaitingForPrev && readyToResume()) {
mTopResumedActivity.scheduleTopResumedActivityChanged(true /* onTop */);
+ mLastReportedTopResumedActivity = mTopResumedActivity;
}
}
@@ -2611,6 +2616,10 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
*/
void endDeferResume() {
mDeferResumeCount--;
+ if (readyToResume() && mLastReportedTopResumedActivity != null
+ && mTopResumedActivity != mLastReportedTopResumedActivity) {
+ scheduleTopResumedActivityStateLossIfNeeded();
+ }
}
/** @return True if resume can be called. */
diff --git a/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java b/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
index fbf9478b4fd9..9754595581a0 100644
--- a/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
@@ -16,10 +16,9 @@
package com.android.server.wm;
-import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
-import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA;
import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA;
@@ -33,7 +32,6 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLAS
import static com.android.server.wm.AppCompatUtils.isChangeEnabled;
import android.annotation.NonNull;
-import android.app.CameraCompatTaskInfo.FreeformCameraCompatMode;
import com.android.server.wm.utils.OptPropFactory;
import com.android.window.flags.Flags;
@@ -165,13 +163,13 @@ class AppCompatCameraOverrides {
*
* <p>The treatment is enabled when the following conditions are met:
* <ul>
- * <li>Property gating the camera compatibility free-form treatment is enabled.
- * <li>Activity isn't opted out by the device manufacturer with override.
+ * <li>Feature flag gating the camera compatibility free-form treatment is enabled.
+ * <li>Activity is opted in by the device manufacturer with override.
* </ul>
*/
boolean shouldApplyFreeformTreatmentForCameraCompat() {
- return Flags.enableCameraCompatForDesktopWindowing() && !isChangeEnabled(mActivityRecord,
- OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT);
+ return Flags.enableCameraCompatForDesktopWindowing() && isChangeEnabled(mActivityRecord,
+ OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT);
}
boolean isOverrideOrientationOnlyForCameraEnabled() {
@@ -202,22 +200,10 @@ class AppCompatCameraOverrides {
&& !mActivityRecord.shouldCreateAppCompatDisplayInsets();
}
- @FreeformCameraCompatMode
- int getFreeformCameraCompatMode() {
- return mAppCompatCameraOverridesState.mFreeformCameraCompatMode;
- }
-
- void setFreeformCameraCompatMode(@FreeformCameraCompatMode int freeformCameraCompatMode) {
- mAppCompatCameraOverridesState.mFreeformCameraCompatMode = freeformCameraCompatMode;
- }
-
static class AppCompatCameraOverridesState {
// Whether activity "refresh" was requested but not finished in
// ActivityRecord#activityResumedLocked following the camera compat force rotation in
// DisplayRotationCompatPolicy.
private boolean mIsRefreshRequested;
-
- @FreeformCameraCompatMode
- private int mFreeformCameraCompatMode = CAMERA_COMPAT_FREEFORM_NONE;
}
}
diff --git a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
index 2a0252ab06fd..506477f67bfc 100644
--- a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
+++ b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
@@ -38,7 +38,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.CameraCompatTaskInfo;
-import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.view.DisplayInfo;
import android.view.Surface;
@@ -46,7 +45,6 @@ import android.view.Surface;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
import com.android.internal.protolog.WmProtoLogGroups;
-import com.android.window.flags.Flags;
/**
* Policy for camera compatibility freeform treatment.
@@ -124,26 +122,6 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa
return appBoundsChanged || displayRotationChanged;
}
- /**
- * Whether activity is eligible for camera compatibility free-form treatment.
- *
- * <p>The treatment is applied to a fixed-orientation camera activity in free-form windowing
- * mode. The treatment letterboxes or pillarboxes the activity to the expected orientation and
- * provides changes to the camera and display orientation signals to match those expected on a
- * portrait device in that orientation (for example, on a standard phone).
- *
- * <p>The treatment is enabled when the following conditions are met:
- * <ul>
- * <li>Property gating the camera compatibility free-form treatment is enabled.
- * <li>Activity isn't opted out by the device manufacturer with override.
- * </ul>
- */
- @VisibleForTesting
- boolean isCameraCompatForFreeformEnabledForActivity(@NonNull ActivityRecord activity) {
- return Flags.enableCameraCompatForDesktopWindowing() && !activity.info.isChangeEnabled(
- ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT);
- }
-
@Override
public void onCameraOpened(@NonNull ActivityRecord cameraActivity,
@NonNull String cameraId) {
@@ -159,16 +137,10 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa
}
cameraActivity.recomputeConfiguration();
- updateCameraCompatMode(cameraActivity);
cameraActivity.getTask().dispatchTaskInfoChangedIfNeeded(/* force= */ true);
cameraActivity.ensureActivityConfiguration(/* ignoreVisibility= */ false);
}
- private void updateCameraCompatMode(@NonNull ActivityRecord cameraActivity) {
- cameraActivity.mAppCompatController.getAppCompatCameraOverrides()
- .setFreeformCameraCompatMode(getCameraCompatMode(cameraActivity));
- }
-
@Override
public boolean onCameraClosed(@NonNull String cameraId) {
// Top activity in the same task as the camera activity, or `null` if the task is
@@ -277,7 +249,8 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa
boolean isTreatmentEnabledForActivity(@NonNull ActivityRecord activity,
boolean checkOrientation) {
int orientation = activity.getRequestedConfigurationOrientation();
- return isCameraCompatForFreeformEnabledForActivity(activity)
+ return activity.mAppCompatController.getAppCompatCameraOverrides()
+ .shouldApplyFreeformTreatmentForCameraCompat()
&& mCameraStateMonitor.isCameraRunningForActivity(activity)
&& (!checkOrientation || orientation != ORIENTATION_UNDEFINED)
&& activity.inFreeformWindowingMode()
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index 3b2479885f57..f40d636b522a 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -101,8 +101,6 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> {
DisplayArea(WindowManagerService wms, Type type, String name, int featureId) {
super(wms);
- // TODO(display-area): move this up to ConfigurationContainer
- setOverrideOrientation(SCREEN_ORIENTATION_UNSET);
mType = type;
mName = name;
mFeatureId = featureId;
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 81a04af17703..f0e12fec3107 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -2025,22 +2025,9 @@ class RecentTasks {
// Fill in some deprecated values.
rti.id = rti.isRunning ? rti.taskId : INVALID_TASK_ID;
rti.persistentId = rti.taskId;
- rti.lastSnapshotData.set(tr.mLastTaskSnapshotData);
if (!getTasksAllowed) {
Task.trimIneffectiveInfo(tr, rti);
}
-
- // Fill in organized child task info for the task created by organizer.
- if (tr.mCreatedByOrganizer) {
- for (int i = tr.getChildCount() - 1; i >= 0; i--) {
- final Task childTask = tr.getChildAt(i).asTask();
- if (childTask != null && childTask.isOrganized()) {
- final ActivityManager.RecentTaskInfo cti = new ActivityManager.RecentTaskInfo();
- childTask.fillTaskInfo(cti, true /* stripExtras */, tda);
- rti.childrenTaskInfos.add(cti);
- }
- }
- }
return rti;
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index dbc3b76c22a1..9504fc7d4d30 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -128,7 +128,6 @@ import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.Activity;
import android.app.ActivityManager;
-import android.app.ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData;
import android.app.ActivityManager.TaskDescription;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
@@ -256,9 +255,6 @@ class Task extends TaskFragment {
private static final String ATTR_MIN_HEIGHT = "min_height";
private static final String ATTR_PERSIST_TASK_VERSION = "persist_task_version";
private static final String ATTR_WINDOW_LAYOUT_AFFINITY = "window_layout_affinity";
- private static final String ATTR_LAST_SNAPSHOT_TASK_SIZE = "last_snapshot_task_size";
- private static final String ATTR_LAST_SNAPSHOT_CONTENT_INSETS = "last_snapshot_content_insets";
- private static final String ATTR_LAST_SNAPSHOT_BUFFER_SIZE = "last_snapshot_buffer_size";
// How long to wait for all background Activities to redraw following a call to
// convertToTranslucent().
@@ -472,10 +468,6 @@ class Task extends TaskFragment {
// NOTE: This value needs to be persisted with each task
private TaskDescription mTaskDescription;
- // Information about the last snapshot that should be persisted with the task to allow SystemUI
- // to layout without loading all the task snapshots
- final PersistedTaskSnapshotData mLastTaskSnapshotData;
-
/** @see #setCanAffectSystemUiFlags */
private boolean mCanAffectSystemUiFlags = true;
@@ -635,14 +627,13 @@ class Task extends TaskFragment {
ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset,
boolean _autoRemoveRecents, int _userId, int _effectiveUid,
String _lastDescription, long lastTimeMoved, boolean neverRelinquishIdentity,
- TaskDescription _lastTaskDescription, PersistedTaskSnapshotData _lastSnapshotData,
- int taskAffiliation, int prevTaskId, int nextTaskId, int callingUid,
- String callingPackage, @Nullable String callingFeatureId, int resizeMode,
- boolean supportsPictureInPicture, boolean _realActivitySuspended,
- boolean userSetupComplete, int minWidth, int minHeight, ActivityInfo info,
- IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor,
- boolean _createdByOrganizer, IBinder _launchCookie, boolean _deferTaskAppear,
- boolean _removeWithTaskOrganizer) {
+ TaskDescription _lastTaskDescription, int taskAffiliation, int prevTaskId,
+ int nextTaskId, int callingUid, String callingPackage,
+ @Nullable String callingFeatureId, int resizeMode, boolean supportsPictureInPicture,
+ boolean _realActivitySuspended, boolean userSetupComplete, int minWidth, int minHeight,
+ ActivityInfo info, IVoiceInteractionSession _voiceSession,
+ IVoiceInteractor _voiceInteractor, boolean _createdByOrganizer, IBinder _launchCookie,
+ boolean _deferTaskAppear, boolean _removeWithTaskOrganizer) {
super(atmService, null /* fragmentToken */, _createdByOrganizer, false /* isEmbedded */);
mTaskId = _taskId;
@@ -652,9 +643,6 @@ class Task extends TaskFragment {
mTaskDescription = _lastTaskDescription != null
? _lastTaskDescription
: new TaskDescription();
- mLastTaskSnapshotData = _lastSnapshotData != null
- ? _lastSnapshotData
- : new PersistedTaskSnapshotData();
affinityIntent = _affinityIntent;
affinity = _affinity;
rootAffinity = _rootAffinity;
@@ -3111,7 +3099,6 @@ class Task extends TaskFragment {
}
void onSnapshotChanged(TaskSnapshot snapshot) {
- mLastTaskSnapshotData.set(snapshot);
mAtmService.getTaskChangeNotificationController().notifyTaskSnapshotChanged(
mTaskId, snapshot);
}
@@ -3423,8 +3410,8 @@ class Task extends TaskFragment {
? top.getLastParentBeforePip().mTaskId : INVALID_TASK_ID;
info.shouldDockBigOverlays = top != null && top.shouldDockBigOverlays;
info.mTopActivityLocusId = top != null ? top.getLocusId() : null;
- info.isTopActivityLimitSystemEducationDialogs = top != null
- ? top.mShouldLimitSystemEducationDialogs : false;
+ info.topActivityRequestOpenInBrowserEducationTimestamp = top != null
+ ? top.mRequestOpenInBrowserEducationTimestamp : 0;
final Task parentTask = getParent() != null ? getParent().asTask() : null;
info.parentTaskId = parentTask != null && parentTask.mCreatedByOrganizer
? parentTask.mTaskId
@@ -3523,6 +3510,7 @@ class Task extends TaskFragment {
info.capturedLink = null;
info.capturedLinkTimestamp = 0;
+ info.topActivityRequestOpenInBrowserEducationTimestamp = 0;
}
@Nullable PictureInPictureParams getPictureInPictureParams() {
@@ -3989,19 +3977,6 @@ class Task extends TaskFragment {
out.attributeInt(null, ATTR_MIN_HEIGHT, mMinHeight);
out.attributeInt(null, ATTR_PERSIST_TASK_VERSION, PERSIST_TASK_VERSION);
- if (mLastTaskSnapshotData.taskSize != null) {
- out.attribute(null, ATTR_LAST_SNAPSHOT_TASK_SIZE,
- mLastTaskSnapshotData.taskSize.flattenToString());
- }
- if (mLastTaskSnapshotData.contentInsets != null) {
- out.attribute(null, ATTR_LAST_SNAPSHOT_CONTENT_INSETS,
- mLastTaskSnapshotData.contentInsets.flattenToString());
- }
- if (mLastTaskSnapshotData.bufferSize != null) {
- out.attribute(null, ATTR_LAST_SNAPSHOT_BUFFER_SIZE,
- mLastTaskSnapshotData.bufferSize.flattenToString());
- }
-
if (affinityIntent != null) {
out.startTag(null, TAG_AFFINITYINTENT);
affinityIntent.saveToXml(out);
@@ -4068,7 +4043,6 @@ class Task extends TaskFragment {
int taskId = INVALID_TASK_ID;
final int outerDepth = in.getDepth();
TaskDescription taskDescription = new TaskDescription();
- PersistedTaskSnapshotData lastSnapshotData = new PersistedTaskSnapshotData();
int taskAffiliation = INVALID_TASK_ID;
int prevTaskId = INVALID_TASK_ID;
int nextTaskId = INVALID_TASK_ID;
@@ -4175,15 +4149,6 @@ class Task extends TaskFragment {
case ATTR_PERSIST_TASK_VERSION:
persistTaskVersion = Integer.parseInt(attrValue);
break;
- case ATTR_LAST_SNAPSHOT_TASK_SIZE:
- lastSnapshotData.taskSize = Point.unflattenFromString(attrValue);
- break;
- case ATTR_LAST_SNAPSHOT_CONTENT_INSETS:
- lastSnapshotData.contentInsets = Rect.unflattenFromString(attrValue);
- break;
- case ATTR_LAST_SNAPSHOT_BUFFER_SIZE:
- lastSnapshotData.bufferSize = Point.unflattenFromString(attrValue);
- break;
default:
if (!attrName.startsWith(TaskDescription.ATTR_TASKDESCRIPTION_PREFIX)) {
Slog.w(TAG, "Task: Unknown attribute=" + attrName);
@@ -4277,7 +4242,6 @@ class Task extends TaskFragment {
.setLastTimeMoved(lastTimeOnTop)
.setNeverRelinquishIdentity(neverRelinquishIdentity)
.setLastTaskDescription(taskDescription)
- .setLastSnapshotData(lastSnapshotData)
.setTaskAffiliation(taskAffiliation)
.setPrevAffiliateTaskId(prevTaskId)
.setNextAffiliateTaskId(nextTaskId)
@@ -6443,7 +6407,6 @@ class Task extends TaskFragment {
private long mLastTimeMoved;
private boolean mNeverRelinquishIdentity;
private TaskDescription mLastTaskDescription;
- private PersistedTaskSnapshotData mLastSnapshotData;
private int mTaskAffiliation;
private int mPrevAffiliateTaskId = INVALID_TASK_ID;
private int mNextAffiliateTaskId = INVALID_TASK_ID;
@@ -6671,11 +6634,6 @@ class Task extends TaskFragment {
return this;
}
- private Builder setLastSnapshotData(PersistedTaskSnapshotData lastSnapshotData) {
- mLastSnapshotData = lastSnapshotData;
- return this;
- }
-
private Builder setOrigActivity(ComponentName origActivity) {
mOrigActivity = origActivity;
return this;
@@ -6824,7 +6782,7 @@ class Task extends TaskFragment {
return new Task(mAtmService, mTaskId, mIntent, mAffinityIntent, mAffinity,
mRootAffinity, mRealActivity, mOrigActivity, mRootWasReset, mAutoRemoveRecents,
mUserId, mEffectiveUid, mLastDescription, mLastTimeMoved,
- mNeverRelinquishIdentity, mLastTaskDescription, mLastSnapshotData,
+ mNeverRelinquishIdentity, mLastTaskDescription,
mTaskAffiliation, mPrevAffiliateTaskId, mNextAffiliateTaskId, mCallingUid,
mCallingPackage, mCallingFeatureId, mResizeMode, mSupportsPictureInPicture,
mRealActivitySuspended, mUserSetupComplete, mMinWidth, mMinHeight,
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 5f92bb626154..2397e032fcc8 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -1676,30 +1676,36 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
* @param orientation the specified orientation. Needs to be one of {@link ScreenOrientation}.
* @param requestingContainer the container which orientation request has changed. Mostly used
* to ensure it gets correct configuration.
+ * @return the resolved override orientation of this window container.
*/
- void setOrientation(@ScreenOrientation int orientation,
+ @ScreenOrientation
+ int setOrientation(@ScreenOrientation int orientation,
@Nullable WindowContainer requestingContainer) {
if (getOverrideOrientation() == orientation) {
- return;
+ return orientation;
}
-
setOverrideOrientation(orientation);
final WindowContainer parent = getParent();
- if (parent != null) {
- if (getConfiguration().orientation != getRequestedConfigurationOrientation()
- // Update configuration directly only if the change won't be dispatched from
- // ancestor. This prevents from computing intermediate configuration when the
- // parent also needs to be updated from the ancestor. E.g. the app requests
- // portrait but the task is still in landscape. While updating from display,
- // the task can be updated to portrait first so the configuration can be
- // computed in a consistent environment.
- && (inMultiWindowMode()
+ if (parent == null) {
+ return orientation;
+ }
+ // The derived class can return a result that is different from the given orientation.
+ final int resolvedOrientation = getOverrideOrientation();
+ if (getConfiguration().orientation != getRequestedConfigurationOrientation(
+ false /* forDisplay */, resolvedOrientation)
+ // Update configuration directly only if the change won't be dispatched from
+ // ancestor. This prevents from computing intermediate configuration when the
+ // parent also needs to be updated from the ancestor. E.g. the app requests
+ // portrait but the task is still in landscape. While updating from display,
+ // the task can be updated to portrait first so the configuration can be
+ // computed in a consistent environment.
+ && (inMultiWindowMode()
|| !handlesOrientationChangeFromDescendant(orientation))) {
- // Resolve the requested orientation.
- onConfigurationChanged(parent.getConfiguration());
- }
- onDescendantOrientationChanged(requestingContainer);
+ // Resolve the requested orientation.
+ onConfigurationChanged(parent.getConfiguration());
}
+ onDescendantOrientationChanged(requestingContainer);
+ return resolvedOrientation;
}
@ScreenOrientation
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 091896590b6b..c42aa37d847b 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -788,9 +788,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
deferResume = false;
// Already calls ensureActivityConfig
mService.mRootWindowContainer.ensureActivitiesVisible();
- if (!mService.mRootWindowContainer.resumeFocusedTasksTopActivities()) {
- mService.mTaskSupervisor.updateTopResumedActivityIfNeeded("endWCT-effects");
- }
+ mService.mRootWindowContainer.resumeFocusedTasksTopActivities();
} else if ((effects & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {
for (int i = haveConfigChanges.size() - 1; i >= 0; --i) {
haveConfigChanges.valueAt(i).forAllActivities(r -> {
@@ -816,10 +814,6 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */);
if (deferResume) {
mService.mTaskSupervisor.endDeferResume();
- // Transient launching the Recents via HIERARCHY_OP_TYPE_PENDING_INTENT directly
- // resume the Recents activity with no TRANSACT_EFFECTS_LIFECYCLE. Explicitly
- // checks if the top resumed activity should be updated after defer-resume ended.
- mService.mTaskSupervisor.updateTopResumedActivityIfNeeded("endWCT");
}
mService.continueWindowLayout();
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 81af78e5ce25..6009848f9308 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -182,7 +182,6 @@ import static com.android.server.wm.WindowStateProto.UNRESTRICTED_KEEP_CLEAR_ARE
import static com.android.server.wm.WindowStateProto.VIEW_VISIBILITY;
import static com.android.server.wm.WindowStateProto.WINDOW_CONTAINER;
import static com.android.server.wm.WindowStateProto.WINDOW_FRAMES;
-import static com.android.window.flags.Flags.secureWindowState;
import static com.android.window.flags.Flags.surfaceTrustedOverlay;
import android.annotation.CallSuper;
@@ -1187,9 +1186,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
if (surfaceTrustedOverlay() && isWindowTrustedOverlay()) {
getPendingTransaction().setTrustedOverlay(mSurfaceControl, true);
}
- if (secureWindowState()) {
- getPendingTransaction().setSecure(mSurfaceControl, isSecureLocked());
- }
+ getPendingTransaction().setSecure(mSurfaceControl, isSecureLocked());
// All apps should be considered as occluding when computing TrustedPresentation Thresholds.
final boolean canOccludePresentation = !mSession.mCanAddInternalSystemWindow;
getPendingTransaction().setCanOccludePresentation(mSurfaceControl, canOccludePresentation);
@@ -6174,18 +6171,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
void setSecureLocked(boolean isSecure) {
ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isSecure=%b: %s", isSecure, getName());
- if (secureWindowState()) {
- if (mSurfaceControl == null) {
- return;
- }
- getPendingTransaction().setSecure(mSurfaceControl, isSecure);
- } else {
- if (mWinAnimator.mSurfaceControl == null) {
- return;
- }
- getPendingTransaction().setSecure(mWinAnimator.mSurfaceControl,
- isSecure);
+ if (mSurfaceControl == null) {
+ return;
}
+ getPendingTransaction().setSecure(mSurfaceControl, isSecure);
if (mDisplayContent != null) {
mDisplayContent.refreshImeSecureFlag(getSyncTransaction());
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index a934eea690f7..0154d95d888d 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -49,7 +49,6 @@ import static com.android.server.wm.WindowStateAnimatorProto.DRAW_STATE;
import static com.android.server.wm.WindowStateAnimatorProto.SURFACE;
import static com.android.server.wm.WindowStateAnimatorProto.SYSTEM_DECOR_RECT;
import static com.android.server.wm.WindowSurfaceControllerProto.SHOWN;
-import static com.android.window.flags.Flags.secureWindowState;
import static com.android.window.flags.Flags.setScPropertiesInClient;
import android.content.Context;
@@ -310,12 +309,6 @@ class WindowStateAnimator {
int flags = SurfaceControl.HIDDEN;
final WindowManager.LayoutParams attrs = w.mAttrs;
- if (!secureWindowState()) {
- if (w.isSecureLocked()) {
- flags |= SurfaceControl.SECURE;
- }
- }
-
if ((mWin.mAttrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0) {
flags |= SurfaceControl.SKIP_SCREENSHOT;
}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index e38337540ad9..dece612c9424 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -2330,6 +2330,12 @@ static void nativeToggleCapsLock(JNIEnv* env, jobject nativeImplObj, jint device
im->getInputManager()->getReader().toggleCapsLockState(deviceId);
}
+static void resetLockedModifierState(JNIEnv* env, jobject nativeImplObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+
+ im->getInputManager()->getReader().resetLockedModifierState();
+}
+
static void nativeDisplayRemoved(JNIEnv* env, jobject nativeImplObj, jint displayId) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -3134,6 +3140,7 @@ static const JNINativeMethod gInputManagerMethods[] = {
{"verifyInputEvent", "(Landroid/view/InputEvent;)Landroid/view/VerifiedInputEvent;",
(void*)nativeVerifyInputEvent},
{"toggleCapsLock", "(I)V", (void*)nativeToggleCapsLock},
+ {"resetLockedModifierState", "()V", (void*)resetLockedModifierState},
{"displayRemoved", "(I)V", (void*)nativeDisplayRemoved},
{"setFocusedApplication", "(ILandroid/view/InputApplicationHandle;)V",
(void*)nativeSetFocusedApplication},
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index da478f38498b..dc10dd9f47a2 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -251,6 +251,7 @@ import com.android.server.security.KeyAttestationApplicationIdProviderService;
import com.android.server.security.KeyChainSystemService;
import com.android.server.security.advancedprotection.AdvancedProtectionService;
import com.android.server.security.authenticationpolicy.AuthenticationPolicyService;
+import com.android.server.security.authenticationpolicy.SecureLockDeviceService;
import com.android.server.security.forensic.ForensicService;
import com.android.server.security.rkp.RemoteProvisioningService;
import com.android.server.selinux.SelinuxAuditLogsService;
@@ -2659,6 +2660,12 @@ public final class SystemServer implements Dumpable {
mSystemServiceManager.startService(AuthService.class);
t.traceEnd();
+ if (android.security.Flags.secureLockdown()) {
+ t.traceBegin("StartSecureLockDeviceService.Lifecycle");
+ mSystemServiceManager.startService(SecureLockDeviceService.Lifecycle.class);
+ t.traceEnd();
+ }
+
if (android.adaptiveauth.Flags.enableAdaptiveAuth()) {
t.traceBegin("StartAuthenticationPolicyService");
mSystemServiceManager.startService(AuthenticationPolicyService.class);
@@ -3062,7 +3069,10 @@ public final class SystemServer implements Dumpable {
if (com.android.ranging.flags.Flags.rangingStackEnabled()) {
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_UWB)
|| context.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_WIFI_RTT)) {
+ PackageManager.FEATURE_WIFI_RTT)
+ || (com.android.ranging.flags.Flags.rangingCsEnabled()
+ && context.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_BLUETOOTH_LE_CHANNEL_SOUNDING))) {
t.traceBegin("RangingService");
// TODO: b/375264320 - Remove after RELEASE_RANGING_STACK is ramped to next.
try {
diff --git a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java
index a547d0f94ea3..4e9fff230bac 100644
--- a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java
@@ -31,6 +31,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
import static org.testng.Assert.expectThrows;
@@ -96,6 +97,7 @@ public class BackupManagerServiceRoboTest {
@UserIdInt private int mUserTwoId;
@Mock private UserBackupManagerService mUserSystemService;
@Mock private UserBackupManagerService mUserOneService;
+ @Mock private BackupAgentConnectionManager mUserOneBackupAgentConnectionManager;
@Mock private UserBackupManagerService mUserTwoService;
/** Setup */
@@ -116,6 +118,9 @@ public class BackupManagerServiceRoboTest {
mShadowContext.grantPermissions(BACKUP);
mShadowContext.grantPermissions(INTERACT_ACROSS_USERS_FULL);
+ when(mUserOneService.getBackupAgentConnectionManager()).thenReturn(
+ mUserOneBackupAgentConnectionManager);
+
ShadowBinder.setCallingUid(Process.SYSTEM_UID);
}
@@ -226,7 +231,7 @@ public class BackupManagerServiceRoboTest {
backupManagerService.agentConnected(mUserOneId, TEST_PACKAGE, agentBinder);
- verify(mUserOneService).agentConnected(TEST_PACKAGE, agentBinder);
+ verify(mUserOneBackupAgentConnectionManager).agentConnected(TEST_PACKAGE, agentBinder);
}
/** Test that the backup service does not route methods for non-registered users. */
@@ -239,7 +244,8 @@ public class BackupManagerServiceRoboTest {
backupManagerService.agentConnected(mUserTwoId, TEST_PACKAGE, agentBinder);
- verify(mUserOneService, never()).agentConnected(TEST_PACKAGE, agentBinder);
+ verify(mUserOneBackupAgentConnectionManager, never()).agentConnected(TEST_PACKAGE,
+ agentBinder);
}
/** Test that the backup service routes methods correctly to the user that requests it. */
diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
index 7349c14ef62b..aeb1ba93f049 100644
--- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
@@ -107,6 +107,7 @@ import com.android.internal.backup.IBackupTransport;
import com.android.internal.infra.AndroidFuture;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
+import com.android.server.backup.BackupAgentConnectionManager;
import com.android.server.backup.BackupRestoreTask;
import com.android.server.backup.DataChangedJournal;
import com.android.server.backup.KeyValueBackupJob;
@@ -167,7 +168,6 @@ import java.util.List;
import java.util.concurrent.TimeoutException;
import java.util.stream.Stream;
-// TODO: Test agents timing out
@RunWith(RobolectricTestRunner.class)
@Config(
shadows = {
@@ -195,6 +195,7 @@ public class KeyValueBackupTaskTest {
@Mock private IBackupManagerMonitor mMonitor;
@Mock private OnTaskFinishedListener mListener;
@Mock private PackageManagerInternal mPackageManagerInternal;
+ @Mock private BackupAgentConnectionManager mBackupAgentConnectionManager;
private UserBackupManagerService mBackupManagerService;
private TransportData mTransport;
@@ -257,6 +258,8 @@ public class KeyValueBackupTaskTest {
when(mBackupManagerService.getBaseStateDir()).thenReturn(mBaseStateDir);
when(mBackupManagerService.getDataDir()).thenReturn(mDataDir);
when(mBackupManagerService.getBackupManagerBinder()).thenReturn(mBackupManager);
+ when(mBackupManagerService.getBackupAgentConnectionManager()).thenReturn(
+ mBackupAgentConnectionManager);
mBackupHandler = mBackupManagerService.getBackupHandler();
mShadowBackupLooper = shadowOf(mBackupHandler.getLooper());
@@ -749,7 +752,8 @@ public class KeyValueBackupTaskTest {
/**
* Agent unavailable means {@link
- * UserBackupManagerService#bindToAgentSynchronous(ApplicationInfo, int)} returns {@code null}.
+ * BackupAgentConnectionManager#bindToAgentSynchronous(ApplicationInfo, int, int)} returns
+ * {@code null}.
*
* @see #setUpAgent(PackageData)
*/
@@ -805,7 +809,7 @@ public class KeyValueBackupTaskTest {
TransportMock transportMock = setUpInitializedTransport(mTransport);
setUpAgent(PACKAGE_1);
doThrow(SecurityException.class)
- .when(mBackupManagerService)
+ .when(mBackupAgentConnectionManager)
.bindToAgentSynchronous(argThat(applicationInfo(PACKAGE_1)), anyInt(), anyInt());
KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
@@ -823,7 +827,7 @@ public class KeyValueBackupTaskTest {
TransportMock transportMock = setUpInitializedTransport(mTransport);
setUpAgent(PACKAGE_1);
doThrow(SecurityException.class)
- .when(mBackupManagerService)
+ .when(mBackupAgentConnectionManager)
.bindToAgentSynchronous(argThat(applicationInfo(PACKAGE_1)), anyInt(), anyInt());
KeyValueBackupTask task = createKeyValueBackupTask(transportMock, true, PACKAGE_1);
@@ -861,7 +865,7 @@ public class KeyValueBackupTaskTest {
runTask(task);
verify(mBackupManagerService).setWorkSource(null);
- verify(mBackupManagerService).unbindAgent(argThat(applicationInfo(PACKAGE_1)));
+ verify(mBackupAgentConnectionManager).unbindAgent(argThat(applicationInfo(PACKAGE_1)));
}
@Test
@@ -1097,7 +1101,7 @@ public class KeyValueBackupTaskTest {
runTask(task);
verify(agentMock.agentBinder).fail(any());
- verify(mBackupManagerService).unbindAgent(argThat(applicationInfo(PACKAGE_1)));
+ verify(mBackupAgentConnectionManager).unbindAgent(argThat(applicationInfo(PACKAGE_1)));
}
@Test
@@ -1418,7 +1422,8 @@ public class KeyValueBackupTaskTest {
.isEqualTo("newState".getBytes());
assertCleansUpFiles(mTransport, PM_PACKAGE);
// We don't unbind PM
- verify(mBackupManagerService, never()).unbindAgent(argThat(applicationInfo(PM_PACKAGE)));
+ verify(mBackupAgentConnectionManager, never()).unbindAgent(
+ argThat(applicationInfo(PM_PACKAGE)));
}
@Test
@@ -1439,7 +1444,8 @@ public class KeyValueBackupTaskTest {
runTask(task);
- verify(mBackupManagerService, never()).unbindAgent(argThat(applicationInfo(PM_PACKAGE)));
+ verify(mBackupAgentConnectionManager, never()).unbindAgent(
+ argThat(applicationInfo(PM_PACKAGE)));
}
@Test
@@ -1642,9 +1648,10 @@ public class KeyValueBackupTaskTest {
runTask(task);
- InOrder inOrder = inOrder(agentMock.agent, mBackupManagerService);
+ InOrder inOrder = inOrder(agentMock.agent, mBackupAgentConnectionManager);
inOrder.verify(agentMock.agent).onQuotaExceeded(anyLong(), eq(1234L));
- inOrder.verify(mBackupManagerService).unbindAgent(argThat(applicationInfo(PACKAGE_1)));
+ inOrder.verify(mBackupAgentConnectionManager).unbindAgent(
+ argThat(applicationInfo(PACKAGE_1)));
}
@Test
@@ -2634,12 +2641,12 @@ public class KeyValueBackupTaskTest {
doNothing().when(backupAgentBinder).fail(any());
if (packageData.available) {
doReturn(backupAgentBinder)
- .when(mBackupManagerService)
+ .when(mBackupAgentConnectionManager)
.bindToAgentSynchronous(argThat(applicationInfo(packageData)), anyInt(),
anyInt());
} else {
doReturn(null)
- .when(mBackupManagerService)
+ .when(mBackupAgentConnectionManager)
.bindToAgentSynchronous(argThat(applicationInfo(packageData)), anyInt(),
anyInt());
}
@@ -2976,7 +2983,7 @@ public class KeyValueBackupTaskTest {
private void assertCleansUpFilesAndAgent(TransportData transport, PackageData packageData) {
assertCleansUpFiles(transport, packageData);
- verify(mBackupManagerService).unbindAgent(argThat(applicationInfo(packageData)));
+ verify(mBackupAgentConnectionManager).unbindAgent(argThat(applicationInfo(packageData)));
}
private void assertCleansUpFiles(TransportData transport, PackageData packageData) {
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
index 5bb6b19cd63e..d08715586580 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
@@ -194,7 +194,13 @@ public class InputMethodServiceTest {
() -> assertThat(mActivity.hideImeWithWindowInsetsController()).isTrue(),
true /* expected */,
false /* inputViewStarted */);
- assertThat(mInputMethodService.isInputViewShown()).isFalse();
+ if (mFlagsValueProvider.getBoolean(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)) {
+ // The IME visibility is only sent at the end of the animation. Therefore, we have to
+ // wait until the visibility was sent to the server and the IME window hidden.
+ eventually(() -> assertThat(mInputMethodService.isInputViewShown()).isFalse());
+ } else {
+ assertThat(mInputMethodService.isInputViewShown()).isFalse();
+ }
}
/**
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/BackupAgentConnectionManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/BackupAgentConnectionManagerTest.java
new file mode 100644
index 000000000000..19e43b6fa851
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/BackupAgentConnectionManagerTest.java
@@ -0,0 +1,381 @@
+/*
+ * 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.backup;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.ApplicationThreadConstants;
+import android.app.IActivityManager;
+import android.app.IBackupAgent;
+import android.app.backup.BackupAnnotations.BackupDestination;
+import android.app.backup.BackupAnnotations.OperationType;
+import android.compat.testing.PlatformCompatChangeRule;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Process;
+import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+import com.android.server.backup.internal.LifecycleOperationStorage;
+
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
+import com.google.common.collect.ImmutableSet;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.util.Set;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class BackupAgentConnectionManagerTest {
+ private static final String TEST_PACKAGE = "com.test.package";
+
+ @Rule
+ public TestRule compatChangeRule = new PlatformCompatChangeRule();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Mock
+ IActivityManager mActivityManager;
+ @Mock
+ ActivityManagerInternal mActivityManagerInternal;
+ @Mock
+ LifecycleOperationStorage mOperationStorage;
+ @Mock
+ UserBackupManagerService mUserBackupManagerService;
+ @Mock
+ IBackupAgent.Stub mBackupAgentStub;
+ @Mock
+ PackageManager mPackageManager;
+
+ private BackupAgentConnectionManager mConnectionManager;
+ private MockitoSession mSession;
+ private ApplicationInfo mTestApplicationInfo;
+ private IBackupAgent mBackupAgentResult;
+ private Thread mTestThread;
+
+ @Before
+ public void setUp() throws Exception {
+ mSession = mockitoSession().initMocks(this).mockStatic(ActivityManager.class).mockStatic(
+ LocalServices.class).strictness(Strictness.LENIENT).startMocking();
+ MockitoAnnotations.initMocks(this);
+
+ doReturn(mActivityManager).when(ActivityManager::getService);
+ doReturn(mActivityManagerInternal).when(
+ () -> LocalServices.getService(ActivityManagerInternal.class));
+ // Real package manager throws if a property is not defined.
+ when(mPackageManager.getPropertyAsUser(any(), any(), any(), anyInt())).thenThrow(
+ new PackageManager.NameNotFoundException());
+
+ mConnectionManager = spy(
+ new BackupAgentConnectionManager(mOperationStorage, mPackageManager,
+ mUserBackupManagerService, UserHandle.USER_SYSTEM));
+
+ mTestApplicationInfo = new ApplicationInfo();
+ mTestApplicationInfo.packageName = TEST_PACKAGE;
+
+ mBackupAgentResult = null;
+ mTestThread = null;
+ }
+
+ @After
+ public void tearDown() {
+ if (mSession != null) {
+ mSession.finishMocking();
+ }
+ }
+
+ @Test
+ public void bindToAgentSynchronous_amReturnsFailure_returnsNullAndClearsPendingBackups()
+ throws Exception {
+ when(mActivityManager.bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(),
+ anyInt(), anyBoolean())).thenReturn(false);
+
+ IBackupAgent result = mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
+
+ assertThat(result).isNull();
+ verify(mActivityManagerInternal).clearPendingBackup(UserHandle.USER_SYSTEM);
+ }
+
+ @Test
+ public void bindToAgentSynchronous_agentDisconnectedCalled_returnsNullAndClearsPendingBackups()
+ throws Exception {
+ when(mActivityManager.bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(),
+ anyInt(), anyBoolean())).thenReturn(true);
+ // This is so that IBackupAgent.Stub.asInterface() works.
+ when(mBackupAgentStub.queryLocalInterface(any())).thenReturn(mBackupAgentStub);
+ when(mConnectionManager.getCallingUid()).thenReturn(Process.SYSTEM_UID);
+
+ // This is going to block until it receives the callback so we need to run it on a
+ // separate thread.
+ Thread testThread = new Thread(() -> setBackupAgentResult(
+ mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD)),
+ "backup-agent-connection-manager-test");
+ testThread.start();
+ // Give the testThread a head start, otherwise agentConnected() might run before
+ // bindToAgentSynchronous() is called.
+ Thread.sleep(500);
+ mConnectionManager.agentDisconnected(TEST_PACKAGE);
+ testThread.join();
+
+ assertThat(mBackupAgentResult).isNull();
+ verify(mActivityManagerInternal).clearPendingBackup(UserHandle.USER_SYSTEM);
+ }
+
+ @Test
+ public void bindToAgentSynchronous_agentConnectedCalled_returnsBackupAgent() throws Exception {
+ when(mActivityManager.bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(),
+ anyInt(), anyBoolean())).thenReturn(true);
+ // This is so that IBackupAgent.Stub.asInterface() works.
+ when(mBackupAgentStub.queryLocalInterface(any())).thenReturn(mBackupAgentStub);
+ when(mConnectionManager.getCallingUid()).thenReturn(Process.SYSTEM_UID);
+
+ // This is going to block until it receives the callback so we need to run it on a
+ // separate thread.
+ Thread testThread = new Thread(() -> setBackupAgentResult(
+ mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD)),
+ "backup-agent-connection-manager-test");
+ testThread.start();
+ // Give the testThread a head start, otherwise agentConnected() might run before
+ // bindToAgentSynchronous() is called.
+ Thread.sleep(500);
+ mConnectionManager.agentConnected(TEST_PACKAGE, mBackupAgentStub);
+ testThread.join();
+
+ assertThat(mBackupAgentResult).isEqualTo(mBackupAgentStub);
+ verify(mActivityManagerInternal, never()).clearPendingBackup(anyInt());
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ public void bindToAgentSynchronous_restrictedModeChangesFlagOff_shouldUseRestrictedMode()
+ throws Exception {
+ mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
+
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(true));
+ // Make sure we never hit the code that checks the property.
+ verify(mPackageManager, never()).getPropertyAsUser(any(), any(), any(), anyInt());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ public void bindToAgentSynchronous_keyValueBackup_shouldNotUseRestrictedMode()
+ throws Exception {
+ mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL, BackupDestination.CLOUD);
+
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(false));
+ // Make sure we never hit the code that checks the property.
+ verify(mPackageManager, never()).getPropertyAsUser(any(), any(), any(), anyInt());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ public void bindToAgentSynchronous_keyValueRestore_shouldNotUseRestrictedMode()
+ throws Exception {
+ mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_RESTORE, BackupDestination.CLOUD);
+
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(false));
+ // Make sure we never hit the code that checks the property.
+ verify(mPackageManager, never()).getPropertyAsUser(any(), any(), any(), anyInt());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ public void bindToAgentSynchronous_packageOptedIn_shouldUseRestrictedMode() throws Exception {
+ reset(mPackageManager);
+ when(mPackageManager.getPropertyAsUser(
+ eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE), eq(TEST_PACKAGE), any(),
+ anyInt())).thenReturn(new PackageManager.Property(
+ PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE, /* value= */ true,
+ TEST_PACKAGE, /* className= */ null));
+
+ mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
+
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(true));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ public void bindToAgentSynchronous_packageOptedOut_shouldNotUseRestrictedMode()
+ throws Exception {
+ reset(mPackageManager);
+ when(mPackageManager.getPropertyAsUser(
+ eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE), eq(TEST_PACKAGE), any(),
+ anyInt())).thenReturn(new PackageManager.Property(
+ PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE, /* value= */ false,
+ TEST_PACKAGE, /* className= */ null));
+
+ mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
+
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(false));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ @DisableCompatChanges({BackupAgentConnectionManager.OS_DECIDES_BACKUP_RESTRICTED_MODE})
+ public void bindToAgentSynchronous_targetSdkBelowB_shouldUseRestrictedMode() throws Exception {
+ reset(mPackageManager);
+ // Mock that the app has not explicitly set the property.
+ when(mPackageManager.getPropertyAsUser(
+ eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE), eq(TEST_PACKAGE), any(),
+ anyInt())).thenThrow(new PackageManager.NameNotFoundException());
+
+ mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
+
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(true));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ @EnableCompatChanges({BackupAgentConnectionManager.OS_DECIDES_BACKUP_RESTRICTED_MODE})
+ public void bindToAgentSynchronous_targetSdkB_notInList_shouldUseRestrictedMode()
+ throws Exception {
+ reset(mPackageManager);
+ // Mock that the app has not explicitly set the property.
+ when(mPackageManager.getPropertyAsUser(
+ eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE), eq(TEST_PACKAGE), any(),
+ anyInt())).thenThrow(new PackageManager.NameNotFoundException());
+ mConnectionManager.clearNoRestrictedModePackages();
+
+ mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
+
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(true));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ @EnableCompatChanges({BackupAgentConnectionManager.OS_DECIDES_BACKUP_RESTRICTED_MODE})
+ public void bindToAgentSynchronous_forRestore_targetSdkB_inList_shouldNotUseRestrictedMode()
+ throws Exception {
+ reset(mPackageManager);
+ // Mock that the app has not explicitly set the property.
+ when(mPackageManager.getPropertyAsUser(
+ eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE), eq(TEST_PACKAGE), any(),
+ anyInt())).thenThrow(new PackageManager.NameNotFoundException());
+ mConnectionManager.setNoRestrictedModePackages(Set.of(TEST_PACKAGE), OperationType.RESTORE);
+
+ mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL, BackupDestination.CLOUD);
+
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(false));
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
+ @EnableCompatChanges({BackupAgentConnectionManager.OS_DECIDES_BACKUP_RESTRICTED_MODE})
+ public void bindToAgentSynchronous_forBackup_targetSdkB_inList_shouldNotUseRestrictedMode()
+ throws Exception {
+ reset(mPackageManager);
+ // Mock that the app has not explicitly set the property.
+ when(mPackageManager.getPropertyAsUser(
+ eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE), eq(TEST_PACKAGE), any(),
+ anyInt())).thenThrow(new PackageManager.NameNotFoundException());
+ mConnectionManager.setNoRestrictedModePackages(Set.of(TEST_PACKAGE), OperationType.BACKUP);
+
+ mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
+
+ verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
+ /* useRestrictedMode= */ eq(false));
+ }
+
+ @Test
+ public void agentDisconnected_cancelsCurrentOperations() throws Exception {
+ when(mOperationStorage.operationTokensForPackage(eq(TEST_PACKAGE))).thenReturn(
+ ImmutableSet.of(123, 456, 789));
+ when(mConnectionManager.getThreadForCancellation(any())).thenAnswer(invocation -> {
+ Thread testThread = new Thread((Runnable) invocation.getArgument(0),
+ "agent-disconnected-test");
+ setTestThread(testThread);
+ return testThread;
+ });
+
+ mConnectionManager.agentDisconnected(TEST_PACKAGE);
+
+ mTestThread.join();
+ verify(mUserBackupManagerService).handleCancel(eq(123), eq(true));
+ verify(mUserBackupManagerService).handleCancel(eq(456), eq(true));
+ verify(mUserBackupManagerService).handleCancel(eq(789), eq(true));
+ }
+
+ @Test
+ public void unbindAgent_callsAmUnbindBackupAgent() throws Exception {
+ mConnectionManager.unbindAgent(mTestApplicationInfo);
+
+ verify(mActivityManager).unbindBackupAgent(eq(mTestApplicationInfo));
+ }
+
+ // Needed because variables can't be assigned directly inside lambdas in Java.
+ private void setBackupAgentResult(IBackupAgent result) {
+ mBackupAgentResult = result;
+ }
+
+ // Needed because variables can't be assigned directly inside lambdas in Java.
+ private void setTestThread(Thread thread) {
+ mTestThread = thread;
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
index 07f2188d30eb..7a9e96f1bc4c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -18,20 +18,18 @@ package com.android.server.backup;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.fail;
+
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.never;
import static org.mockito.Mockito.when;
import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
-import android.app.ApplicationThreadConstants;
import android.app.IActivityManager;
import android.app.backup.BackupAgent;
import android.app.backup.BackupAnnotations.BackupDestination;
@@ -47,8 +45,6 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Handler;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
@@ -57,7 +53,6 @@ import android.util.FeatureFlagUtils;
import android.util.KeyValueListParser;
import androidx.test.core.app.ApplicationProvider;
-import androidx.test.filters.FlakyTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.backup.internal.BackupHandler;
@@ -69,8 +64,6 @@ import com.android.server.backup.transport.TransportConnection;
import com.android.server.backup.utils.BackupEligibilityRules;
import com.android.server.backup.utils.BackupManagerMonitorEventSender;
-import com.google.common.collect.ImmutableSet;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -84,11 +77,6 @@ import org.mockito.quality.Strictness;
import java.util.Arrays;
import java.util.List;
-import java.util.Set;
-import java.util.function.IntConsumer;
-
-import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
-import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
@Presubmit
@RunWith(AndroidJUnit4.class)
@@ -96,7 +84,6 @@ public class UserBackupManagerServiceTest {
private static final String TEST_PACKAGE = "package1";
private static final String[] TEST_PACKAGES = new String[] { TEST_PACKAGE };
private static final String TEST_TRANSPORT = "transport";
- private static final int WORKER_THREAD_TIMEOUT_MILLISECONDS = 100;
@UserIdInt private static final int USER_ID = 0;
@Rule
@@ -278,33 +265,6 @@ public class UserBackupManagerServiceTest {
}
@Test
- @FlakyTest
- public void testAgentDisconnected_cancelsCurrentOperations() throws Exception {
- when(mOperationStorage.operationTokensForPackage(eq("com.android.foo"))).thenReturn(
- ImmutableSet.of(123, 456, 789)
- );
-
- mService.agentDisconnected("com.android.foo");
-
- mService.waitForAsyncOperation();
- verify(mOperationStorage).cancelOperation(eq(123), eq(true), any(IntConsumer.class));
- verify(mOperationStorage).cancelOperation(eq(456), eq(true), any());
- verify(mOperationStorage).cancelOperation(eq(789), eq(true), any());
- }
-
- @Test
- public void testAgentDisconnected_unknownPackageName_cancelsNothing() throws Exception {
- when(mOperationStorage.operationTokensForPackage(eq("com.android.foo"))).thenReturn(
- ImmutableSet.of()
- );
-
- mService.agentDisconnected("com.android.foo");
-
- verify(mOperationStorage, never())
- .cancelOperation(anyInt(), anyBoolean(), any(IntConsumer.class));
- }
-
- @Test
public void testReportDelayedRestoreResult_sendsLogsToMonitor() throws Exception {
PackageInfo packageInfo = getPackageInfo(TEST_PACKAGE);
when(mPackageManager.getPackageInfoAsUser(anyString(),
@@ -324,158 +284,6 @@ public class UserBackupManagerServiceTest {
eq(packageInfo), eq(results), eq(OperationType.RESTORE));
}
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
- public void bindToAgentSynchronous_restrictedModeChangesFlagOff_shouldUseRestrictedMode()
- throws Exception {
- mService.bindToAgentSynchronous(mTestPackageApplicationInfo,
- ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
-
- verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
- /* useRestrictedMode= */ eq(true));
- // Make sure we never hit the code that checks the property.
- verify(mPackageManager, never()).getPropertyAsUser(any(), any(), any(), anyInt());
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
- public void bindToAgentSynchronous_keyValueBackup_shouldNotUseRestrictedMode()
- throws Exception {
- mService.bindToAgentSynchronous(mTestPackageApplicationInfo,
- ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL, BackupDestination.CLOUD);
-
- verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
- /* useRestrictedMode= */ eq(false));
- // Make sure we never hit the code that checks the property.
- verify(mPackageManager, never()).getPropertyAsUser(any(), any(), any(), anyInt());
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
- public void bindToAgentSynchronous_keyValueRestore_shouldNotUseRestrictedMode()
- throws Exception {
- mService.bindToAgentSynchronous(mTestPackageApplicationInfo,
- ApplicationThreadConstants.BACKUP_MODE_RESTORE, BackupDestination.CLOUD);
-
- verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
- /* useRestrictedMode= */ eq(false));
- // Make sure we never hit the code that checks the property.
- verify(mPackageManager, never()).getPropertyAsUser(any(), any(), any(), anyInt());
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
- public void bindToAgentSynchronous_packageOptedIn_shouldUseRestrictedMode()
- throws Exception {
- when(mPackageManager.getPropertyAsUser(
- eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE),
- eq(TEST_PACKAGE), any(), anyInt())).thenReturn(new PackageManager.Property(
- PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE, /* value= */ true,
- TEST_PACKAGE, /* className= */ null));
-
- mService.bindToAgentSynchronous(mTestPackageApplicationInfo,
- ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
-
- verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
- /* useRestrictedMode= */ eq(true));
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
- public void bindToAgentSynchronous_packageOptedOut_shouldNotUseRestrictedMode()
- throws Exception {
- when(mPackageManager.getPropertyAsUser(
- eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE),
- eq(TEST_PACKAGE), any(), anyInt())).thenReturn(new PackageManager.Property(
- PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE, /* value= */ false,
- TEST_PACKAGE, /* className= */ null));
-
- mService.bindToAgentSynchronous(mTestPackageApplicationInfo,
- ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
-
- verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
- /* useRestrictedMode= */ eq(false));
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
- @DisableCompatChanges({UserBackupManagerService.OS_DECIDES_BACKUP_RESTRICTED_MODE})
- public void bindToAgentSynchronous_targetSdkBelowB_shouldUseRestrictedMode()
- throws Exception {
- // Mock that the app has not explicitly set the property.
- when(mPackageManager.getPropertyAsUser(
- eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE),
- eq(TEST_PACKAGE), any(), anyInt())).thenThrow(
- new PackageManager.NameNotFoundException()
- );
-
- mService.bindToAgentSynchronous(mTestPackageApplicationInfo,
- ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
-
- verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
- /* useRestrictedMode= */ eq(true));
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
- @EnableCompatChanges({UserBackupManagerService.OS_DECIDES_BACKUP_RESTRICTED_MODE})
- public void bindToAgentSynchronous_targetSdkB_notInList_shouldUseRestrictedMode()
- throws Exception {
- // Mock that the app has not explicitly set the property.
- when(mPackageManager.getPropertyAsUser(
- eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE),
- eq(TEST_PACKAGE), any(), anyInt())).thenThrow(
- new PackageManager.NameNotFoundException()
- );
- mService.clearNoRestrictedModePackages();
-
- mService.bindToAgentSynchronous(mTestPackageApplicationInfo,
- ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
-
- verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
- /* useRestrictedMode= */ eq(true));
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
- @EnableCompatChanges({UserBackupManagerService.OS_DECIDES_BACKUP_RESTRICTED_MODE})
- public void bindToAgentSynchronous_forRestore_targetSdkB_inList_shouldNotUseRestrictedMode()
- throws Exception {
- // Mock that the app has not explicitly set the property.
- when(mPackageManager.getPropertyAsUser(
- eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE),
- eq(TEST_PACKAGE), any(), anyInt())).thenThrow(
- new PackageManager.NameNotFoundException()
- );
- mService.setNoRestrictedModePackages(Set.of(TEST_PACKAGE), OperationType.RESTORE);
-
- mService.bindToAgentSynchronous(mTestPackageApplicationInfo,
- ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL, BackupDestination.CLOUD);
-
- verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
- /* useRestrictedMode= */ eq(false));
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
- @EnableCompatChanges({UserBackupManagerService.OS_DECIDES_BACKUP_RESTRICTED_MODE})
- public void bindToAgentSynchronous_forBackup_targetSdkB_inList_shouldNotUseRestrictedMode()
- throws Exception {
- // Mock that the app has not explicitly set the property.
- when(mPackageManager.getPropertyAsUser(
- eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE),
- eq(TEST_PACKAGE), any(), anyInt())).thenThrow(
- new PackageManager.NameNotFoundException()
- );
- mService.setNoRestrictedModePackages(Set.of(TEST_PACKAGE), OperationType.BACKUP);
-
- mService.bindToAgentSynchronous(mTestPackageApplicationInfo,
- ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD);
-
- verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(),
- /* useRestrictedMode= */ eq(false));
- }
-
private static PackageInfo getPackageInfo(String packageName) {
PackageInfo packageInfo = new PackageInfo();
packageInfo.applicationInfo = new ApplicationInfo();
@@ -487,8 +295,6 @@ public class UserBackupManagerServiceTest {
boolean isEnabledStatePersisted = false;
boolean shouldUseNewBackupEligibilityRules = false;
- private volatile Thread mWorkerThread = null;
-
TestBackupService() {
super(mContext, mPackageManager, mOperationStorage, mTransportManager, mBackupHandler,
createConstants(mContext), mActivityManager, mActivityManagerInternal);
@@ -523,26 +329,8 @@ public class UserBackupManagerServiceTest {
}
@Override
- Thread getThreadForAsyncOperation(String operationName, Runnable operation) {
- mWorkerThread = super.getThreadForAsyncOperation(operationName, operation);
- return mWorkerThread;
- }
-
- @Override
BackupManagerMonitorEventSender getBMMEventSender(IBackupManagerMonitor monitor) {
return mBackupManagerMonitorEventSender;
}
-
- private void waitForAsyncOperation() {
- if (mWorkerThread == null) {
- return;
- }
-
- try {
- mWorkerThread.join(/* millis */ WORKER_THREAD_TIMEOUT_MILLISECONDS);
- } catch (InterruptedException e) {
- fail("Failed waiting for worker thread to complete: " + e.getMessage());
- }
- }
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java
index 331057398949..e618433862f2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java
@@ -33,6 +33,7 @@ import android.platform.test.annotations.Presubmit;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.backup.BackupAgentConnectionManager;
import com.android.server.backup.BackupAgentTimeoutParameters;
import com.android.server.backup.OperationStorage;
import com.android.server.backup.TransportManager;
@@ -66,6 +67,8 @@ public class PerformFullTransportBackupTaskTest {
@Mock
UserBackupManagerService mBackupManagerService;
@Mock
+ BackupAgentConnectionManager mBackupAgentConnectionManager;
+ @Mock
BackupTransportClient mBackupTransportClient;
@Mock
CountDownLatch mLatch;
@@ -95,6 +98,8 @@ public class PerformFullTransportBackupTaskTest {
when(mBackupManagerService.isSetupComplete()).thenReturn(true);
when(mBackupManagerService.getAgentTimeoutParameters()).thenReturn(
mBackupAgentTimeoutParameters);
+ when(mBackupManagerService.getBackupAgentConnectionManager()).thenReturn(
+ mBackupAgentConnectionManager);
when(mBackupManagerService.getTransportManager()).thenReturn(mTransportManager);
when(mTransportManager.getCurrentTransportClient(any())).thenReturn(mTransportConnection);
when(mTransportConnection.connectOrThrow(any())).thenReturn(mBackupTransportClient);
@@ -142,11 +147,11 @@ public class PerformFullTransportBackupTaskTest {
mTask.run();
- InOrder inOrder = inOrder(mBackupManagerService);
- inOrder.verify(mBackupManagerService).setNoRestrictedModePackages(
+ InOrder inOrder = inOrder(mBackupAgentConnectionManager);
+ inOrder.verify(mBackupAgentConnectionManager).setNoRestrictedModePackages(
eq(Set.of("package1")),
eq(BackupAnnotations.OperationType.BACKUP));
- inOrder.verify(mBackupManagerService).clearNoRestrictedModePackages();
+ inOrder.verify(mBackupAgentConnectionManager).clearNoRestrictedModePackages();
}
private void createTask(String[] packageNames) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
index 055adf68ee0f..351aac357c44 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
@@ -44,6 +44,7 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.modules.utils.testing.TestableDeviceConfig;
+import com.android.server.backup.BackupAgentConnectionManager;
import com.android.server.backup.Flags;
import com.android.server.backup.UserBackupManagerService;
import com.android.server.backup.internal.BackupHandler;
@@ -95,6 +96,8 @@ public class PerformUnifiedRestoreTaskTest {
private TransportConnection mTransportConnection;
@Mock
private BackupTransportClient mBackupTransportClient;
+ @Mock
+ private BackupAgentConnectionManager mBackupAgentConnectionManager;
private Set<String> mExcludedkeys = new HashSet<>();
private Map<String, String> mBackupData = new HashMap<>();
@@ -122,6 +125,9 @@ public class PerformUnifiedRestoreTaskTest {
mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ when(mBackupManagerService.getBackupAgentConnectionManager()).thenReturn(
+ mBackupAgentConnectionManager);
+
mBackupDataSource = new ArrayDeque<>(mBackupData.keySet());
when(mBackupDataInput.readNextHeader())
.then((Answer<Boolean>) invocation -> !mBackupDataSource.isEmpty());
@@ -166,7 +172,7 @@ public class PerformUnifiedRestoreTaskTest {
mRestoreTask.setNoRestrictedModePackages(mBackupTransportClient,
new PackageInfo[]{packageInfo1, packageInfo2});
- verify(mBackupManagerService).setNoRestrictedModePackages(
+ verify(mBackupAgentConnectionManager).setNoRestrictedModePackages(
eq(Set.of("package1")),
eq(BackupAnnotations.OperationType.RESTORE));
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index d66bb00ae879..c6870adb8464 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -65,6 +65,7 @@ import android.app.job.JobInfo;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManagerInternal;
+import android.compat.testing.PlatformCompatChangeRule;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -103,10 +104,13 @@ import com.android.server.job.controllers.QuotaController.TimedEvent;
import com.android.server.job.controllers.QuotaController.TimingSession;
import com.android.server.usage.AppStandbyInternal;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
@@ -135,6 +139,9 @@ public class QuotaControllerTest {
private static final int SOURCE_USER_ID = 0;
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Rule
+ public TestRule compatChangeRule = new PlatformCompatChangeRule();
private QuotaController mQuotaController;
private QuotaController.QcConstants mQcConstants;
private JobSchedulerService.Constants mConstants = new JobSchedulerService.Constants();
@@ -303,7 +310,7 @@ public class QuotaControllerTest {
private int getProcessStateQuotaFreeThreshold() {
synchronized (mQuotaController.mLock) {
- return mQuotaController.getProcessStateQuotaFreeThreshold();
+ return mQuotaController.getProcessStateQuotaFreeThreshold(mSourceUid);
}
}
@@ -5197,6 +5204,101 @@ public class QuotaControllerTest {
assertFalse(jobBg2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
}
+ @Test
+ @EnableCompatChanges({QuotaController.OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS,
+ QuotaController.OVERRIDE_QUOTA_ENFORCEMENT_TO_FGS_JOBS})
+ @RequiresFlagsEnabled({Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS,
+ Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_FGS_JOBS})
+ public void testTracking_OutOfQuota_ForegroundAndBackground_CompactChangeOverrides() {
+ setDischarging();
+
+ JobStatus jobBg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 1);
+ JobStatus jobTop = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 2);
+ trackJobs(jobBg, jobTop);
+ setStandbyBucket(WORKING_INDEX, jobTop, jobBg); // 2 hour window
+ // Now the package only has 20 seconds to run.
+ final long remainingTimeMs = 20 * SECOND_IN_MILLIS;
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(
+ JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS,
+ 10 * MINUTE_IN_MILLIS - remainingTimeMs, 1), false);
+
+ InOrder inOrder = inOrder(mJobSchedulerService);
+
+ // UID starts out inactive.
+ setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
+ // Start the job.
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobBg);
+ }
+ advanceElapsedClock(remainingTimeMs / 2);
+ // New job starts after UID is in the foreground. Since the app is now in the foreground, it
+ // should continue to have remainingTimeMs / 2 time remaining.
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobTop);
+ }
+ advanceElapsedClock(remainingTimeMs);
+
+ // Wait for some extra time to allow for job processing.
+ inOrder.verify(mJobSchedulerService,
+ timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
+ .onControllerStateChanged(argThat(jobs -> jobs.size() > 0));
+ synchronized (mQuotaController.mLock) {
+ assertEquals(remainingTimeMs / 2,
+ mQuotaController.getRemainingExecutionTimeLocked(jobBg));
+ assertEquals(remainingTimeMs / 2,
+ mQuotaController.getRemainingExecutionTimeLocked(jobTop));
+ }
+ // Go to a background state.
+ setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
+ advanceElapsedClock(remainingTimeMs / 2 + 1);
+ // Only Bg job will be changed from in-quota to out-of-quota.
+ inOrder.verify(mJobSchedulerService,
+ timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
+ .onControllerStateChanged(argThat(jobs -> jobs.size() == 1));
+ // Top job should still be allowed to run.
+ assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+
+ // New jobs to run.
+ JobStatus jobBg2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 3);
+ JobStatus jobTop2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 4);
+ JobStatus jobFg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 5);
+ setStandbyBucket(WORKING_INDEX, jobBg2, jobTop2, jobFg);
+
+ advanceElapsedClock(20 * SECOND_IN_MILLIS);
+ setProcessState(ActivityManager.PROCESS_STATE_TOP);
+ inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
+ .onControllerStateChanged(argThat(jobs -> jobs.size() == 1));
+ trackJobs(jobFg, jobTop);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobTop);
+ }
+ assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+
+ // App still in foreground so everything should be in quota.
+ advanceElapsedClock(20 * SECOND_IN_MILLIS);
+ setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+
+ advanceElapsedClock(20 * SECOND_IN_MILLIS);
+ setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
+ inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
+ .onControllerStateChanged(argThat(jobs -> jobs.size() == 2));
+ // App is now in background and out of quota. Fg should now change to out of quota since it
+ // wasn't started. Top should remain in quota since it started when the app was in TOP.
+ assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertFalse(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ trackJobs(jobBg2);
+ assertFalse(jobBg2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ }
+
/**
* Tests that TOP jobs are stopped when an app runs out of quota.
*/
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockPowerStatsCollectorTest.java
index 0d5d277b00ef..ed927c6ab699 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockPowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockPowerStatsCollectorTest.java
@@ -26,8 +26,7 @@ import android.content.Context;
import android.os.Process;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
-import android.platform.test.ravenwood.RavenwoodConfig;
-import android.platform.test.ravenwood.RavenwoodConfig.Config;
+import android.platform.test.ravenwood.RavenwoodRule;
import com.android.internal.os.PowerStats;
import com.android.server.power.feature.flags.Flags;
@@ -39,10 +38,9 @@ import org.junit.Test;
public class WakelockPowerStatsCollectorTest {
- @Config
- public static final RavenwoodConfig sConfig =
- new RavenwoodConfig.Builder()
- .setProvideMainThread(true)
+ @Rule
+ public final RavenwoodRule mRule =
+ new RavenwoodRule.Builder()
.setSystemPropertyImmutable(
"persist.sys.com.android.server.power.feature.flags."
+ "framework_wakelock_info-override",
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index 510c2bcabad0..b2a7d20fb948 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -664,7 +664,7 @@ public class MediaProjectionManagerServiceTest {
mClockInjector);
MediaProjectionManagerService.MediaProjection projection = createProjectionPreconditions(
service);
- mClock.fastForward(projection.mDefaultTimeoutMs + 10);
+ mClock.fastForward(projection.mDefaultTimeoutMillis + 10);
// Immediate timeout - so no longer valid.
assertThat(projection.isValid()).isFalse();
diff --git a/services/tests/servicestests/src/com/android/server/security/authenticationpolicy/AuthenticationPolicyServiceTest.java b/services/tests/servicestests/src/com/android/server/security/authenticationpolicy/AuthenticationPolicyServiceTest.java
index 2238a1be97a1..ee8eb9b35088 100644
--- a/services/tests/servicestests/src/com/android/server/security/authenticationpolicy/AuthenticationPolicyServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/security/authenticationpolicy/AuthenticationPolicyServiceTest.java
@@ -19,12 +19,14 @@ package com.android.server.security.authenticationpolicy;
import static android.adaptiveauth.Flags.FLAG_ENABLE_ADAPTIVE_AUTH;
import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS;
import static android.security.Flags.FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS;
+import static android.security.authenticationpolicy.AuthenticationPolicyManager.ERROR_UNSUPPORTED;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST;
import static com.android.server.security.authenticationpolicy.AuthenticationPolicyService.MAX_ALLOWED_FAILED_AUTH_ATTEMPTS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -95,6 +97,8 @@ public class AuthenticationPolicyServiceTest {
private WindowManagerInternal mWindowManager;
@Mock
private UserManagerInternal mUserManager;
+ @Mock
+ private SecureLockDeviceServiceInternal mSecureLockDeviceService;
@Captor
ArgumentCaptor<LockSettingsStateListener> mLockSettingsStateListenerCaptor;
@@ -123,6 +127,11 @@ public class AuthenticationPolicyServiceTest {
LocalServices.addService(WindowManagerInternal.class, mWindowManager);
LocalServices.removeServiceForTest(UserManagerInternal.class);
LocalServices.addService(UserManagerInternal.class, mUserManager);
+ if (android.security.Flags.secureLockdown()) {
+ LocalServices.removeServiceForTest(SecureLockDeviceServiceInternal.class);
+ LocalServices.addService(SecureLockDeviceServiceInternal.class,
+ mSecureLockDeviceService);
+ }
mAuthenticationPolicyService = new AuthenticationPolicyService(
mContext, mLockPatternUtils);
@@ -136,6 +145,12 @@ public class AuthenticationPolicyServiceTest {
// Set PRIMARY_USER_ID as the parent of MANAGED_PROFILE_USER_ID
when(mUserManager.getProfileParentId(eq(MANAGED_PROFILE_USER_ID)))
.thenReturn(PRIMARY_USER_ID);
+ if (android.security.Flags.secureLockdown()) {
+ when(mSecureLockDeviceService.enableSecureLockDevice(any()))
+ .thenReturn(ERROR_UNSUPPORTED);
+ when(mSecureLockDeviceService.disableSecureLockDevice(any()))
+ .thenReturn(ERROR_UNSUPPORTED);
+ }
}
@After
@@ -143,6 +158,9 @@ public class AuthenticationPolicyServiceTest {
LocalServices.removeServiceForTest(LockSettingsInternal.class);
LocalServices.removeServiceForTest(WindowManagerInternal.class);
LocalServices.removeServiceForTest(UserManagerInternal.class);
+ if (android.security.Flags.secureLockdown()) {
+ LocalServices.removeServiceForTest(SecureLockDeviceServiceInternal.class);
+ }
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 704c1b858b8d..e6b4bc98ea4c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -17178,8 +17178,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
@EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
public void testSetCanBePromoted_granted() throws Exception {
- mContext.getTestablePermissions().setPermission(
- android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
// qualifying posted notification
Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
.setSmallIcon(android.R.drawable.sym_def_app_icon)
@@ -17254,8 +17252,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
@EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
public void testSetCanBePromoted_granted_onlyNotifiesOnce() throws Exception {
- mContext.getTestablePermissions().setPermission(
- android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
// qualifying posted notification
Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
.setSmallIcon(android.R.drawable.sym_def_app_icon)
@@ -17285,8 +17281,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
@EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
public void testSetCanBePromoted_revoked() throws Exception {
- mContext.getTestablePermissions().setPermission(
- android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
// start from true state
mBinderService.setCanBePromoted(mPkg, mUid, true, true);
@@ -17350,8 +17344,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
@EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
public void testSetCanBePromoted_revoked_onlyNotifiesOnce() throws Exception {
- mContext.getTestablePermissions().setPermission(
- android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
// start from true state
mBinderService.setCanBePromoted(mPkg, mUid, true, true);
@@ -17387,8 +17379,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
public void testPostPromotableNotification() throws Exception {
mBinderService.setCanBePromoted(mPkg, mUid, true, true);
assertThat(mBinderService.appCanBePromoted(mPkg, mUid)).isTrue();
- mContext.getTestablePermissions().setPermission(
- android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
.setSmallIcon(android.R.drawable.sym_def_app_icon)
@@ -17415,8 +17405,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
@EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
public void testPostPromotableNotification_noPermission() throws Exception {
- mContext.getTestablePermissions().setPermission(
- android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
.setSmallIcon(android.R.drawable.sym_def_app_icon)
.setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
@@ -17444,8 +17432,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
public void testPostPromotableNotification_unimportantNotification() throws Exception {
mBinderService.setCanBePromoted(mPkg, mUid, true, true);
- mContext.getTestablePermissions().setPermission(
- android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
Notification n = new Notification.Builder(mContext, mMinChannel.getId())
.setSmallIcon(android.R.drawable.sym_def_app_icon)
.setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index 6e6b70d319ab..5f2f3ed67432 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -17,10 +17,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.frameworks.wmtests">
- <!-- Uses API introduced in P (28) -->
+ <!-- Uses API introduced in S (31). Using SDK 31+ avoids Google Play Protect popups. -->
<uses-sdk
android:minSdkVersion="1"
- android:targetSdkVersion="28" />
+ android:targetSdkVersion="31" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
index 7f260f85a755..70f57eb40385 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
@@ -357,6 +357,25 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase {
assertEquals(activity1.app, mAtm.mTopApp);
}
+ @Test
+ public void testTopResumedActivity_deferResume() {
+ final ActivityRecord activity1 = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final ActivityRecord activity2 = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ activity2.setState(ActivityRecord.State.RESUMED, "test");
+ assertEquals(activity2.app, mAtm.mTopApp);
+ reset(activity2);
+
+ // Verify that no top-resumed activity changes to the client while defer-resume enabled.
+ mSupervisor.beginDeferResume();
+ activity1.getTask().moveToFront("test");
+ activity1.setState(ActivityRecord.State.RESUMED, "test");
+ verify(activity2, never()).scheduleTopResumedActivityChanged(eq(false));
+
+ // Verify that the change is scheduled to the client after defer-resumed disabled
+ mSupervisor.endDeferResume();
+ verify(activity2).scheduleTopResumedActivityChanged(eq(false));
+ }
+
/**
* We need to launch home again after user unlocked for those displays that do not have
* encryption aware home app.
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
index b91a5b7afe26..d5ed048032e7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
@@ -17,8 +17,8 @@
package com.android.server.wm;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
-import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA;
import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA;
@@ -228,9 +228,8 @@ public class AppCompatCameraOverridesTest extends WindowTestsBase {
}
@Test
- @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT})
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
- public void testShouldApplyCameraCompatFreeformTreatment_overrideEnabled_returnsFalse() {
+ public void testShouldApplyCameraCompatFreeformTreatment_notEnabledByOverride_returnsFalse() {
runTestScenario((robot) -> {
robot.activity().createActivityWithComponentInNewTask();
@@ -239,19 +238,9 @@ public class AppCompatCameraOverridesTest extends WindowTestsBase {
}
@Test
- @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT})
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
- public void testShouldApplyCameraCompatFreeformTreatment_disabledByOverride_returnsFalse() {
- runTestScenario((robot) -> {
- robot.activity().createActivityWithComponentInNewTask();
-
- robot.checkShouldApplyFreeformTreatmentForCameraCompat(false);
- });
- }
-
- @Test
- @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
- public void testShouldApplyCameraCompatFreeformTreatment_notDisabledByOverride_returnsTrue() {
+ public void testShouldApplyCameraCompatFreeformTreatment_overrideAndFlagEnabled_returnsTrue() {
runTestScenario((robot) -> {
robot.activity().createActivityWithComponentInNewTask();
@@ -261,8 +250,9 @@ public class AppCompatCameraOverridesTest extends WindowTestsBase {
@Test
@EnableCompatChanges({OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA,
- OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA})
- public void testShouldRecomputeConfigurationForCameraCompat() {
+ OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA,
+ OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
+ public void testShouldRecomputeConfigurationForFreeformTreatment() {
runTestScenario((robot) -> {
robot.conf().enableCameraCompatSplitScreenAspectRatio(true);
robot.applyOnActivity((a) -> {
diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
index e447565a55bd..c427583d3001 100644
--- a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
@@ -25,7 +25,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
-import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT;
+import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_FULL_USER;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
@@ -96,61 +96,55 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
private static final String TEST_PACKAGE_1 = "com.android.frameworks.wmtests";
private static final String TEST_PACKAGE_2 = "com.test.package.two";
private static final String CAMERA_ID_1 = "camera-1";
- private static final String CAMERA_ID_2 = "camera-2";
- private CameraManager mMockCameraManager;
- private Handler mMockHandler;
private AppCompatConfiguration mAppCompatConfiguration;
private CameraManager.AvailabilityCallback mCameraAvailabilityCallback;
private CameraCompatFreeformPolicy mCameraCompatFreeformPolicy;
private ActivityRecord mActivity;
- private Task mTask;
private ActivityRefresher mActivityRefresher;
@Before
public void setUp() throws Exception {
mAppCompatConfiguration = mDisplayContent.mWmService.mAppCompatConfiguration;
spyOn(mAppCompatConfiguration);
- when(mAppCompatConfiguration.isCameraCompatTreatmentEnabled())
- .thenReturn(true);
- when(mAppCompatConfiguration.isCameraCompatRefreshEnabled())
- .thenReturn(true);
+ when(mAppCompatConfiguration.isCameraCompatTreatmentEnabled()).thenReturn(true);
+ when(mAppCompatConfiguration.isCameraCompatRefreshEnabled()).thenReturn(true);
when(mAppCompatConfiguration.isCameraCompatRefreshCycleThroughStopEnabled())
.thenReturn(true);
- mMockCameraManager = mock(CameraManager.class);
+ final CameraManager mockCameraManager = mock(CameraManager.class);
doAnswer(invocation -> {
mCameraAvailabilityCallback = invocation.getArgument(1);
return null;
- }).when(mMockCameraManager).registerAvailabilityCallback(
+ }).when(mockCameraManager).registerAvailabilityCallback(
any(Executor.class), any(CameraManager.AvailabilityCallback.class));
- when(mContext.getSystemService(CameraManager.class)).thenReturn(mMockCameraManager);
+ when(mContext.getSystemService(CameraManager.class)).thenReturn(mockCameraManager);
mDisplayContent.setIgnoreOrientationRequest(true);
- mMockHandler = mock(Handler.class);
+ final Handler mockHandler = mock(Handler.class);
- when(mMockHandler.postDelayed(any(Runnable.class), anyLong())).thenAnswer(
+ when(mockHandler.postDelayed(any(Runnable.class), anyLong())).thenAnswer(
invocation -> {
((Runnable) invocation.getArgument(0)).run();
return null;
});
- mActivityRefresher = new ActivityRefresher(mDisplayContent.mWmService, mMockHandler);
- CameraStateMonitor cameraStateMonitor =
- new CameraStateMonitor(mDisplayContent, mMockHandler);
- mCameraCompatFreeformPolicy =
- new CameraCompatFreeformPolicy(mDisplayContent, cameraStateMonitor,
- mActivityRefresher);
+ mActivityRefresher = new ActivityRefresher(mDisplayContent.mWmService, mockHandler);
+ final CameraStateMonitor cameraStateMonitor = new CameraStateMonitor(mDisplayContent,
+ mockHandler);
+ mCameraCompatFreeformPolicy = new CameraCompatFreeformPolicy(mDisplayContent,
+ cameraStateMonitor, mActivityRefresher);
setDisplayRotation(Surface.ROTATION_90);
mCameraCompatFreeformPolicy.start();
cameraStateMonitor.startListeningToCameraState();
}
- @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testFullscreen_doesNotActivateCameraCompatMode() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN);
doReturn(false).when(mActivity).inFreeformWindowingMode();
@@ -160,23 +154,26 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
assertNotInCameraCompatMode();
}
- @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testOrientationUnspecified_doesNotActivateCameraCompatMode() {
configureActivity(SCREEN_ORIENTATION_UNSPECIFIED);
assertNotInCameraCompatMode();
}
- @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testNoCameraConnection_doesNotActivateCameraCompatMode() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
assertNotInCameraCompatMode();
}
- @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testCameraConnected_deviceInPortrait_portraitCameraCompatMode() throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
setDisplayRotation(Surface.ROTATION_0);
@@ -187,6 +184,8 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
}
@Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testCameraConnected_deviceInLandscape_portraitCameraCompatMode() throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
setDisplayRotation(Surface.ROTATION_270);
@@ -197,6 +196,8 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
}
@Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testCameraConnected_deviceInPortrait_landscapeCameraCompatMode() throws Exception {
configureActivity(SCREEN_ORIENTATION_LANDSCAPE);
setDisplayRotation(Surface.ROTATION_0);
@@ -207,6 +208,8 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
}
@Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testCameraConnected_deviceInLandscape_landscapeCameraCompatMode() throws Exception {
configureActivity(SCREEN_ORIENTATION_LANDSCAPE);
setDisplayRotation(Surface.ROTATION_270);
@@ -216,8 +219,9 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
assertActivityRefreshRequested(/* refreshRequested */ false);
}
- @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testCameraReconnected_cameraCompatModeAndRefresh() throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
setDisplayRotation(Surface.ROTATION_270);
@@ -236,6 +240,8 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
}
@Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testCameraOpenedForDifferentPackage_notInCameraCompatMode() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
@@ -246,27 +252,32 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
- @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT})
- public void testShouldApplyCameraCompatFreeformTreatment_overrideEnabled_returnsFalse() {
+ public void testShouldApplyCameraCompatFreeformTreatment_overrideNotEnabled_returnsFalse() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- assertTrue(mActivity.info
- .isChangeEnabled(OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT));
- assertFalse(mCameraCompatFreeformPolicy.isCameraCompatForFreeformEnabledForActivity(
- mActivity));
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertFalse(mCameraCompatFreeformPolicy.isTreatmentEnabledForActivity(mActivity,
+ /* checkOrientation */ true));
}
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
- public void testShouldApplyCameraCompatFreeformTreatment_notDisabledByOverride_returnsTrue() {
+ @EnableCompatChanges(OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT)
+ public void testShouldApplyCameraCompatFreeformTreatment_enabledByOverride_returnsTrue() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- assertTrue(mCameraCompatFreeformPolicy.isCameraCompatForFreeformEnabledForActivity(
- mActivity));
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertTrue(mActivity.info
+ .isChangeEnabled(OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT));
+ assertTrue(mCameraCompatFreeformPolicy.isTreatmentEnabledForActivity(mActivity,
+ /* checkOrientation */ true));
}
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testShouldRefreshActivity_appBoundsChanged_returnsTrue() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
Configuration oldConfiguration = createConfiguration(/* letterbox= */ false);
@@ -279,6 +290,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testShouldRefreshActivity_displayRotationChanged_returnsTrue() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
Configuration oldConfiguration = createConfiguration(/* letterbox= */ true);
@@ -294,6 +306,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testShouldRefreshActivity_appBoundsNorDisplayChanged_returnsFalse() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
Configuration oldConfiguration = createConfiguration(/* letterbox= */ true);
@@ -309,6 +322,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testOnActivityConfigurationChanging_refreshDisabledViaFlag_noRefresh()
throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
@@ -324,6 +338,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testOnActivityConfigurationChanging_cycleThroughStopDisabled() throws Exception {
when(mAppCompatConfiguration.isCameraCompatRefreshCycleThroughStopEnabled())
.thenReturn(false);
@@ -338,6 +353,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testOnActivityConfigurationChanging_cycleThroughStopDisabledForApp()
throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
@@ -352,6 +368,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testGetCameraCompatAspectRatio_activityNotInCameraCompat_returnsDefaultAspRatio() {
configureActivity(SCREEN_ORIENTATION_FULL_USER);
@@ -365,6 +382,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testGetCameraCompatAspectRatio_activityInCameraCompat_returnsConfigAspectRatio() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
final float configAspectRatio = 1.5f;
@@ -380,6 +398,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
@Test
@EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
public void testGetCameraCompatAspectRatio_inCameraCompatPerAppOverride_returnDefAspectRatio() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
final float configAspectRatio = 1.5f;
@@ -406,7 +425,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
private void configureActivityAndDisplay(@ScreenOrientation int activityOrientation,
@Orientation int naturalOrientation, @WindowingMode int windowingMode) {
- mTask = new TaskBuilder(mSupervisor)
+ final Task task = new TaskBuilder(mSupervisor)
.setDisplay(mDisplayContent)
.setWindowingMode(windowingMode)
.build();
@@ -416,7 +435,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
.setComponent(ComponentName.createRelative(mContext,
com.android.server.wm.CameraCompatFreeformPolicyTests.class.getName()))
.setScreenOrientation(activityOrientation)
- .setTask(mTask)
+ .setTask(task)
.build();
spyOn(mActivity.mAppCompatController.getAppCompatCameraOverrides());
@@ -429,13 +448,11 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
}
private void assertInCameraCompatMode(@CameraCompatTaskInfo.FreeformCameraCompatMode int mode) {
- assertEquals(mode, mActivity.mAppCompatController.getAppCompatCameraOverrides()
- .getFreeformCameraCompatMode());
+ assertEquals(mode, mCameraCompatFreeformPolicy.getCameraCompatMode(mActivity));
}
private void assertNotInCameraCompatMode() {
- assertEquals(CAMERA_COMPAT_FREEFORM_NONE, mActivity.mAppCompatController
- .getAppCompatCameraOverrides().getFreeformCameraCompatMode());
+ assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_NONE);
}
private void assertActivityRefreshRequested(boolean refreshRequested) throws Exception {
@@ -471,7 +488,8 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
private Configuration createConfiguration(boolean letterbox) {
final Configuration configuration = new Configuration();
- Rect bounds = letterbox ? new Rect(300, 0, 700, 600) : new Rect(0, 0, 1000, 600);
+ Rect bounds = letterbox ? new Rect(/*left*/ 300, /*top*/ 0, /*right*/ 700, /*bottom*/ 600)
+ : new Rect(/*left*/ 0, /*top*/ 0, /*right*/ 1000, /*bottom*/ 600);
configuration.windowConfiguration.setAppBounds(bounds);
return configuration;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index df17cd1d24b7..7ed8283efffd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -496,24 +496,6 @@ public class RecentTasksTest extends WindowTestsBase {
}
@Test
- public void testAppendOrganizedChildTaskInfo() {
- final Task root = createTaskBuilder(".CreatedByOrganizerRoot").build();
- root.mCreatedByOrganizer = true;
- // Add organized and non-organized child.
- final Task child1 = createTaskBuilder(".Task1").setParentTask(root).build();
- final Task child2 = createTaskBuilder(".Task2").setParentTask(root).build();
- doReturn(true).when(child1).isOrganized();
- doReturn(false).when(child2).isOrganized();
- mRecentTasks.add(root);
-
- // Make sure only organized child will be appended.
- final List<RecentTaskInfo> infos = getRecentTasks(0 /* flags */);
- final List<RecentTaskInfo> childrenTaskInfos = infos.get(0).childrenTaskInfos;
- assertEquals(childrenTaskInfos.size(), 1);
- assertEquals(childrenTaskInfos.get(0).taskId, child1.mTaskId);
- }
-
- @Test
public void testAddTasksHomeClearUntrackedTasks_expectFinish() {
// There may be multiple tasks with the same base intent by flags (FLAG_ACTIVITY_NEW_TASK |
// FLAG_ACTIVITY_MULTIPLE_TASK). If the previous task is still active, it should be removed
@@ -1420,46 +1402,6 @@ public class RecentTasksTest extends WindowTestsBase {
}
@Test
- public void testLastSnapshotData_snapshotSaved() {
- final TaskSnapshot snapshot = createSnapshot(new Point(100, 100), new Point(80, 80));
- final Task task1 = createTaskBuilder(".Task").build();
- task1.onSnapshotChanged(snapshot);
-
- mRecentTasks.add(task1);
- final List<RecentTaskInfo> infos = getRecentTasks(0 /* flags */);
- final RecentTaskInfo.PersistedTaskSnapshotData lastSnapshotData =
- infos.get(0).lastSnapshotData;
- assertTrue(lastSnapshotData.taskSize.equals(100, 100));
- assertTrue(lastSnapshotData.bufferSize.equals(80, 80));
- }
-
- @Test
- public void testLastSnapshotData_noBuffer() {
- final Task task1 = createTaskBuilder(".Task").build();
- final TaskSnapshot snapshot = createSnapshot(new Point(100, 100), null);
- task1.onSnapshotChanged(snapshot);
-
- mRecentTasks.add(task1);
- final List<RecentTaskInfo> infos = getRecentTasks(0 /* flags */);
- final RecentTaskInfo.PersistedTaskSnapshotData lastSnapshotData =
- infos.get(0).lastSnapshotData;
- assertTrue(lastSnapshotData.taskSize.equals(100, 100));
- assertNull(lastSnapshotData.bufferSize);
- }
-
- @Test
- public void testLastSnapshotData_notSet() {
- final Task task1 = createTaskBuilder(".Task").build();
-
- mRecentTasks.add(task1);
- final List<RecentTaskInfo> infos = getRecentTasks(0 /* flags */);
- final RecentTaskInfo.PersistedTaskSnapshotData lastSnapshotData =
- infos.get(0).lastSnapshotData;
- assertNull(lastSnapshotData.taskSize);
- assertNull(lastSnapshotData.bufferSize);
- }
-
- @Test
public void testCreateRecentTaskInfo_detachedTask() {
final Task task = createTaskBuilder(".Task").build();
final ComponentName componentName = getUniqueComponentName();
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 6490cbe3e31a..7cfdec664a92 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9734,6 +9734,35 @@ public class CarrierConfigManager {
"carrier_supported_satellite_services_per_provider_bundle";
/**
+ * A PersistableBundle that contains a list of key-value pairs, where the values are integer
+ * arrays.
+ * <p>
+ * Keys are the IDs of regional satellite configs as strings and values are
+ * integer arrays of earfcns in the corresponding regions.
+ *
+ * An example config for two regions "1" and "2":
+ * <pre>{@code
+ * <carrier_config>
+ * <pbundle_as_map name="regional_satellite_earfcn_bundle">
+ * <int-array name = "1" num = "2">
+ * <item value = "100"/>
+ * <item value = "200"/>
+ * </int-array>
+ * <int-array name = "2" num = "1">
+ * <item value = "200"/>
+ * </int-array>
+ * </pbundle_as_map>
+ * </carrier_config>
+ * }</pre>
+ * <p>
+ * This config is empty by default.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+ public static final String KEY_REGIONAL_SATELLITE_EARFCN_BUNDLE =
+ "regional_satellite_earfcn_bundle";
+
+ /**
* This config enables modem to scan satellite PLMNs specified as per
* {@link #KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE} and attach to same
* in case cellular networks are not enabled. This will need specific agreement between
@@ -11264,6 +11293,9 @@ public class CarrierConfigManager {
sDefaults.putPersistableBundle(
KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE,
PersistableBundle.EMPTY);
+ sDefaults.putPersistableBundle(
+ KEY_REGIONAL_SATELLITE_EARFCN_BUNDLE,
+ PersistableBundle.EMPTY);
sDefaults.putBoolean(KEY_SATELLITE_ATTACH_SUPPORTED_BOOL, false);
sDefaults.putInt(KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT, 180);
sDefaults.putIntArray(KEY_NTN_LTE_RSRP_THRESHOLDS_INT_ARRAY,
diff --git a/test-mock/src/android/test/mock/MockContentResolver.java b/test-mock/src/android/test/mock/MockContentResolver.java
index 8f4bcccb0cba..af6ee3d0f71b 100644
--- a/test-mock/src/android/test/mock/MockContentResolver.java
+++ b/test-mock/src/android/test/mock/MockContentResolver.java
@@ -24,6 +24,7 @@ import android.content.Context;
import android.content.IContentProvider;
import android.database.ContentObserver;
import android.net.Uri;
+import android.util.Log;
import java.util.Collection;
import java.util.HashMap;
@@ -53,6 +54,7 @@ import java.util.Map;
* </div>
*/
public class MockContentResolver extends ContentResolver {
+ private static final String TAG = "MockContentResolver";
Map<String, ContentProvider> mProviders;
/**
@@ -105,6 +107,7 @@ public class MockContentResolver extends ContentResolver {
if (provider != null) {
return provider.getIContentProvider();
} else {
+ Log.w(TAG, "Provider does not exist: " + name);
return null;
}
}
diff --git a/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java b/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java
index 2cd625eec032..4d495adf727b 100644
--- a/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java
+++ b/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java
@@ -18,7 +18,9 @@ package android.app.jank.tests;
import static org.junit.Assert.assertEquals;
+import android.app.jank.AppJankStats;
import android.app.jank.Flags;
+import android.app.jank.FrameOverrunHistogram;
import android.app.jank.JankDataProcessor;
import android.app.jank.StateTracker;
import android.platform.test.annotations.RequiresFlagsEnabled;
@@ -39,6 +41,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
@RunWith(AndroidJUnit4.class)
@@ -154,6 +157,73 @@ public class JankDataProcessorTest {
assertEquals(totalFrames, histogramFrames);
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void mergeAppJankStats_confirmStatAddedToPendingStats() {
+ HashMap<String, JankDataProcessor.PendingJankStat> pendingStats =
+ mJankDataProcessor.getPendingJankStats();
+
+ assertEquals(pendingStats.size(), 0);
+
+ AppJankStats jankStats = getAppJankStats();
+ mJankDataProcessor.mergeJankStats(jankStats, sActivityName);
+
+ pendingStats = mJankDataProcessor.getPendingJankStats();
+
+ assertEquals(pendingStats.size(), 1);
+ }
+
+ /**
+ * This test confirms matching states are combined into one pending stat. When JankStats are
+ * merged from outside the platform they will contain widget category, widget id and widget
+ * state. If an incoming JankStats matches a pending stat on all those fields the incoming
+ * JankStat will be merged into the existing stat.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void mergeAppJankStats_confirmStatsWithMatchingStatesAreCombinedIntoOnePendingStat() {
+ AppJankStats jankStats = getAppJankStats();
+ mJankDataProcessor.mergeJankStats(jankStats, sActivityName);
+
+ HashMap<String, JankDataProcessor.PendingJankStat> pendingStats =
+ mJankDataProcessor.getPendingJankStats();
+ assertEquals(pendingStats.size(), 1);
+
+ AppJankStats secondJankStat = getAppJankStats();
+ mJankDataProcessor.mergeJankStats(secondJankStat, sActivityName);
+
+ pendingStats = mJankDataProcessor.getPendingJankStats();
+
+ assertEquals(pendingStats.size(), 1);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void mergeAppJankStats_whenStatsWithMatchingStatesMerge_confirmFrameCountsAdded() {
+ AppJankStats jankStats = getAppJankStats();
+ mJankDataProcessor.mergeJankStats(jankStats, sActivityName);
+ mJankDataProcessor.mergeJankStats(jankStats, sActivityName);
+
+ HashMap<String, JankDataProcessor.PendingJankStat> pendingStats =
+ mJankDataProcessor.getPendingJankStats();
+
+ String statKey = pendingStats.keySet().iterator().next();
+ JankDataProcessor.PendingJankStat pendingStat = pendingStats.get(statKey);
+
+ assertEquals(pendingStats.size(), 1);
+ // The same jankStats objects are merged twice, this should result in the frame counts being
+ // doubled.
+ assertEquals(jankStats.getJankyFrameCount() * 2, pendingStat.getJankyFrames());
+ assertEquals(jankStats.getTotalFrameCount() * 2, pendingStat.getTotalFrames());
+
+ int[] originalHistogramBuckets = jankStats.getFrameOverrunHistogram().getBucketCounters();
+ int[] frameOverrunBuckets = pendingStat.getFrameOverrunBuckets();
+
+ for (int i = 0; i < frameOverrunBuckets.length; i++) {
+ assertEquals(originalHistogramBuckets[i] * 2, frameOverrunBuckets[i]);
+ }
+ }
+
// TODO b/375005277 add tests that cover logging and releasing resources back to pool.
private long getTotalFramesCounted() {
@@ -276,4 +346,26 @@ public class JankDataProcessorTest {
return mockData;
}
+ private AppJankStats getAppJankStats() {
+ AppJankStats jankStats = new AppJankStats(
+ /*App Uid*/APP_ID,
+ /*Widget Id*/"test widget id",
+ /*Widget Category*/AppJankStats.SCROLL,
+ /*Widget State*/AppJankStats.SCROLLING,
+ /*Total Frames*/100,
+ /*Janky Frames*/25,
+ getOverrunHistogram()
+ );
+ return jankStats;
+ }
+
+ private FrameOverrunHistogram getOverrunHistogram() {
+ FrameOverrunHistogram overrunHistogram = new FrameOverrunHistogram();
+ overrunHistogram.addFrameOverrunMillis(-2);
+ overrunHistogram.addFrameOverrunMillis(1);
+ overrunHistogram.addFrameOverrunMillis(5);
+ overrunHistogram.addFrameOverrunMillis(25);
+ return overrunHistogram;
+ }
+
}
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
index 2c998c4217ac..64328275085d 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
@@ -92,7 +92,7 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
}
/** Move an app to Desktop by dragging the app handle at the top. */
- fun enterDesktopModeWithDrag(
+ private fun enterDesktopModeWithDrag(
wmHelper: WindowManagerStateHelper,
device: UiDevice,
motionEventHelper: MotionEventHelper = MotionEventHelper(getInstrumentation(), TOUCH)
@@ -155,14 +155,16 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
?: error("Unable to find resource $MINIMIZE_BUTTON_VIEW\n")
}
- fun minimizeDesktopApp(wmHelper: WindowManagerStateHelper, device: UiDevice) {
+ fun minimizeDesktopApp(wmHelper: WindowManagerStateHelper, device: UiDevice, isPip: Boolean = false) {
val caption = getCaptionForTheApp(wmHelper, device)
val minimizeButton = getMinimizeButtonForTheApp(caption)
minimizeButton.click()
wmHelper
.StateSyncBuilder()
.withAppTransitionIdle()
- .withWindowSurfaceDisappeared(innerHelper)
+ .apply {
+ if (isPip) withPipShown() else withWindowSurfaceDisappeared(innerHelper)
+ }
.waitForAndVerify()
}
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/StartMediaProjectionAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/StartMediaProjectionAppHelper.kt
index 69fde0168b14..9e488486e16a 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/StartMediaProjectionAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/StartMediaProjectionAppHelper.kt
@@ -65,10 +65,45 @@ constructor(
.waitForAndVerify()
}
+ fun startSingleAppMediaProjectionWithExtraIntent(
+ wmHelper: WindowManagerStateHelper,
+ targetApp: StandardAppHelper
+ ) {
+ clickStartMediaProjectionWithExtraIntentButton()
+ chooseSingleAppOption()
+ startScreenSharing()
+ selectTargetApp(targetApp.appName)
+ wmHelper
+ .StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withHomeActivityVisible()
+ .waitForAndVerify()
+ }
+
+ fun startSingleAppMediaProjectionFromRecents(
+ wmHelper: WindowManagerStateHelper,
+ targetApp: StandardAppHelper,
+ recentTasksIndex: Int = 0,
+ ) {
+ clickStartMediaProjectionButton()
+ chooseSingleAppOption()
+ startScreenSharing()
+ selectTargetAppRecent(recentTasksIndex)
+ wmHelper
+ .StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withWindowSurfaceAppeared(targetApp)
+ .waitForAndVerify()
+ }
+
private fun clickStartMediaProjectionButton() {
findObject(By.res(packageName, START_MEDIA_PROJECTION_BUTTON_ID)).also { it.click() }
}
+ private fun clickStartMediaProjectionWithExtraIntentButton() {
+ findObject(By.res(packageName, START_MEDIA_PROJECTION_NEW_INTENT_BUTTON_ID)).also { it.click() }
+ }
+
private fun chooseEntireScreenOption() {
findObject(By.res(SCREEN_SHARE_OPTIONS_PATTERN)).also { it.click() }
@@ -92,6 +127,13 @@ constructor(
findObject(By.text(targetAppName)).also { it.click() }
}
+ private fun selectTargetAppRecent(recentTasksIndex: Int) {
+ // Scroll to to find target app to launch then click app icon it to start capture
+ val recentsTasksRecycler =
+ findObject(By.res(SYSTEMUI_PACKAGE, MEDIA_PROJECTION_RECENT_TASKS))
+ recentsTasksRecycler.children[recentTasksIndex].also{ it.click() }
+ }
+
private fun chooseSingleAppOption() {
findObject(By.res(SCREEN_SHARE_OPTIONS_PATTERN)).also { it.click() }
@@ -116,8 +158,10 @@ constructor(
const val TIMEOUT: Long = 5000L
const val ACCEPT_RESOURCE_ID: String = "android:id/button1"
const val START_MEDIA_PROJECTION_BUTTON_ID: String = "button_start_mp"
+ const val START_MEDIA_PROJECTION_NEW_INTENT_BUTTON_ID: String = "button_start_mp_new_intent"
val SCREEN_SHARE_OPTIONS_PATTERN: Pattern =
Pattern.compile("$SYSTEMUI_PACKAGE:id/screen_share_mode_(options|spinner)")
+ const val MEDIA_PROJECTION_RECENT_TASKS: String = "media_projection_recent_tasks_recycler"
const val ENTIRE_SCREEN_STRING_RES_NAME: String =
"screen_share_permission_dialog_option_entire_screen"
const val SINGLE_APP_STRING_RES_NAME: String =
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml
index 46f01e6c9752..c34d2003ef42 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml
@@ -16,17 +16,27 @@
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
android:orientation="vertical"
android:background="@android:color/holo_orange_light">
<Button
android:id="@+id/button_start_mp"
- android:layout_width="500dp"
- android:layout_height="500dp"
+ android:layout_margin="16dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:gravity="center_vertical|center_horizontal"
android:text="Start Media Projection"
android:textAppearance="?android:attr/textAppearanceLarge"/>
+ <Button
+ android:id="@+id/button_start_mp_new_intent"
+ android:layout_margin="16dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical|center_horizontal"
+ android:text="Start Media Projection with extra intent"
+ android:textAppearance="?android:attr/textAppearanceLarge"/>
</LinearLayout> \ No newline at end of file
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/StartMediaProjectionActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/StartMediaProjectionActivity.java
index a24a48269d7c..b29b87450197 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/StartMediaProjectionActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/StartMediaProjectionActivity.java
@@ -19,7 +19,8 @@ package com.android.server.wm.flicker.testapp;
import static com.android.wm.shell.flicker.utils.MediaProjectionUtils.EXTRA_MESSENGER;
import static com.android.wm.shell.flicker.utils.MediaProjectionUtils.MSG_SERVICE_DESTROYED;
import static com.android.wm.shell.flicker.utils.MediaProjectionUtils.MSG_START_FOREGROUND_DONE;
-import static com.android.wm.shell.flicker.utils.MediaProjectionUtils.REQUEST_CODE;
+import static com.android.wm.shell.flicker.utils.MediaProjectionUtils.REQUEST_CODE_NORMAL;
+import static com.android.wm.shell.flicker.utils.MediaProjectionUtils.REQUEST_CODE_EXTRA_INTENT;
import android.app.Activity;
import android.content.ComponentName;
@@ -71,13 +72,17 @@ public class StartMediaProjectionActivity extends Activity {
setContentView(R.layout.activity_start_media_projection);
Button startMediaProjectionButton = findViewById(R.id.button_start_mp);
+ Button startMediaProjectionButton2 = findViewById(R.id.button_start_mp_new_intent);
startMediaProjectionButton.setOnClickListener(v ->
- startActivityForResult(mService.createScreenCaptureIntent(), REQUEST_CODE));
+ startActivityForResult(mService.createScreenCaptureIntent(), REQUEST_CODE_NORMAL));
+ startMediaProjectionButton2.setOnClickListener(v ->
+ startActivityForResult(mService.createScreenCaptureIntent(),
+ REQUEST_CODE_EXTRA_INTENT));
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (requestCode != REQUEST_CODE) {
+ if (requestCode != REQUEST_CODE_NORMAL && requestCode != REQUEST_CODE_EXTRA_INTENT) {
throw new IllegalStateException("Unknown request code: " + requestCode);
}
if (resultCode != RESULT_OK) {
@@ -85,6 +90,11 @@ public class StartMediaProjectionActivity extends Activity {
}
Log.d(TAG, "onActivityResult");
startMediaProjectionService(resultCode, data);
+ if (requestCode == REQUEST_CODE_EXTRA_INTENT) {
+ Intent startMain = new Intent(Intent.ACTION_MAIN);
+ startMain.addCategory(Intent.CATEGORY_HOME);
+ startActivity(startMain);
+ }
}
private void startMediaProjectionService(int resultCode, Intent resultData) {
@@ -122,7 +132,7 @@ public class StartMediaProjectionActivity extends Activity {
displayBounds.width(), displayBounds.height(), PixelFormat.RGBA_8888, 1);
mVirtualDisplay = mMediaProjection.createVirtualDisplay(
- "DanielDisplay",
+ "TestDisplay",
displayBounds.width(),
displayBounds.height(),
DisplayMetrics.DENSITY_HIGH,