summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apct-tests/perftests/multiuser/src/android/multiuser/BroadcastWaiter.java12
-rw-r--r--apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java18
-rw-r--r--apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java2
-rw-r--r--apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java2
-rw-r--r--apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java9
-rw-r--r--core/api/current.txt44
-rw-r--r--core/api/module-lib-current.txt1
-rw-r--r--core/api/system-current.txt36
-rw-r--r--core/java/android/app/ApplicationExitInfo.java9
-rw-r--r--core/java/android/app/ClientTransactionHandler.java18
-rw-r--r--core/java/android/app/ContextImpl.java10
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java82
-rw-r--r--core/java/android/app/admin/DevicePolicyManagerInternal.java25
-rw-r--r--core/java/android/app/admin/PolicyUpdateReason.java74
-rw-r--r--core/java/android/app/admin/PolicyUpdatesReceiver.java315
-rw-r--r--core/java/android/app/admin/TargetUser.java85
-rw-r--r--core/java/android/app/servertransaction/ActivityRelaunchItem.java5
-rw-r--r--core/java/android/content/Context.java15
-rw-r--r--core/java/android/content/ContextWrapper.java8
-rw-r--r--core/java/android/content/pm/ActivityInfo.java13
-rw-r--r--core/java/android/hardware/Camera.java5
-rw-r--r--core/java/android/hardware/camera2/CameraManager.java1
-rw-r--r--core/java/android/hardware/camera2/params/MandatoryStreamCombination.java2
-rw-r--r--core/java/android/hardware/input/IInputManager.aidl12
-rw-r--r--core/java/android/hardware/input/IKeyboardBacklightListener.aidl28
-rw-r--r--core/java/android/hardware/input/IKeyboardBacklightState.aidl27
-rw-r--r--core/java/android/hardware/input/InputManager.java150
-rw-r--r--core/java/android/hardware/input/KeyboardBacklightState.java41
-rw-r--r--core/java/android/os/UserManager.java9
-rw-r--r--core/java/android/provider/Settings.java9
-rw-r--r--core/java/android/service/voice/AbstractDetector.java (renamed from core/java/android/service/voice/AbstractHotwordDetector.java)20
-rw-r--r--core/java/android/service/voice/AlwaysOnHotwordDetector.java2
-rw-r--r--core/java/android/service/voice/SoftwareHotwordDetector.java2
-rw-r--r--core/java/android/service/voice/VisualQueryDetectionService.java198
-rw-r--r--core/java/android/service/voice/VoiceInteractionService.java28
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java2
-rw-r--r--core/java/android/view/EventLogTags.logtags6
-rw-r--r--core/java/android/view/IWindowManager.aidl3
-rw-r--r--core/java/android/view/IWindowSession.aidl6
-rw-r--r--core/java/android/view/InputDevice.java46
-rw-r--r--core/java/android/view/InsetsSourceControl.aidl1
-rw-r--r--core/java/android/view/InsetsSourceControl.java48
-rw-r--r--core/java/android/view/MotionEvent.java17
-rw-r--r--core/java/android/view/ViewRootImpl.java11
-rw-r--r--core/java/android/view/WindowManager.java39
-rw-r--r--core/java/android/view/WindowlessWindowManager.java6
-rw-r--r--core/java/android/view/inputmethod/ImeTracker.java3
-rw-r--r--core/java/android/window/TaskFragmentAnimationParams.aidl23
-rw-r--r--core/java/android/window/TaskFragmentAnimationParams.java129
-rw-r--r--core/java/android/window/TaskFragmentOperation.aidl23
-rw-r--r--core/java/android/window/TaskFragmentOperation.java168
-rw-r--r--core/java/android/window/WindowContainerTransaction.java52
-rw-r--r--core/java/android/window/WindowMetricsController.java42
-rw-r--r--core/jni/android_view_InputDevice.cpp11
-rw-r--r--core/res/AndroidManifest.xml40
-rw-r--r--core/res/res/values/config.xml18
-rw-r--r--core/res/res/values/symbols.xml3
-rw-r--r--core/tests/coretests/src/android/app/activity/ActivityThreadTest.java14
-rw-r--r--core/tests/coretests/src/android/companion/virtual/OWNERS2
-rw-r--r--core/tests/coretests/src/android/hardware/input/KeyboardBacklightListenerTest.kt163
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java38
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java3
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java34
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java23
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java12
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java35
-rw-r--r--libs/WindowManager/Jetpack/window-extensions-release.aarbin30820 -> 32970 bytes
-rw-r--r--libs/WindowManager/Shell/res/color/decor_title_color.xml (renamed from libs/WindowManager/Shell/res/color/decor_caption_title_color.xml)0
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml (renamed from libs/WindowManager/Shell/res/drawable/decor_caption_menu_background.xml)0
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml (renamed from libs/WindowManager/Shell/res/drawable/decor_caption_title.xml)0
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_decor_handle_menu.xml (renamed from libs/WindowManager/Shell/res/layout/caption_handle_menu.xml)2
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml (renamed from libs/WindowManager/Shell/res/layout/caption_window_decoration.xml)4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java54
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt28
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java13
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java50
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java7
-rw-r--r--libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp2
-rw-r--r--media/java/android/media/AudioManager.java111
-rw-r--r--media/java/android/media/IAudioService.aidl7
-rw-r--r--media/java/android/media/IDevicesForAttributesCallback.aidl33
-rw-r--r--media/java/android/media/IMediaRouterService.aidl3
-rw-r--r--media/java/android/media/ImageWriter.java108
-rw-r--r--media/java/android/media/MediaRouter2.java6
-rw-r--r--native/android/input.cpp2
-rw-r--r--packages/DynamicSystemInstallationService/AndroidManifest.xml2
-rw-r--r--packages/PackageInstaller/AndroidManifest.xml3
-rw-r--r--packages/SettingsLib/CollapsingToolbarBaseActivity/AndroidManifest.xml2
-rw-r--r--packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/collapsing_toolbar_base_layout.xml6
-rw-r--r--packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarAppCompatActivity.java179
-rw-r--r--packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java20
-rw-r--r--packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java77
-rw-r--r--packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java12
-rw-r--r--packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml7
-rw-r--r--packages/SettingsLib/MainSwitchPreference/res/values-sw600dp/dmiens.xml22
-rw-r--r--packages/SettingsLib/MainSwitchPreference/res/values-sw720dp-land/dmiens.xml23
-rw-r--r--packages/SettingsLib/MainSwitchPreference/res/values-sw720dp/dmiens.xml23
-rw-r--r--packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml2
-rw-r--r--packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java13
-rw-r--r--packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java24
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml6
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values/styles.xml6
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt15
-rw-r--r--packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/SpinnerScreenshotTest.kt7
-rw-r--r--packages/SettingsLib/Spa/spa/Android.bp1
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt44
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedComposeNavigator.kt79
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavHost.kt267
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavGraphBuilder.kt118
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavHostController.kt41
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt27
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/SpinnerTest.kt20
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt7
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt14
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt34
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt19
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt43
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt41
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt102
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt7
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt8
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt63
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt3
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt67
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt29
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt44
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt214
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java12
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java9
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java5
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java4
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java1
-rw-r--r--packages/Shell/AndroidManifest.xml5
-rw-r--r--packages/SystemUI/AndroidManifest.xml4
-rw-r--r--packages/SystemUI/TEST_MAPPING25
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/Android.bp32
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml33
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/OWNERS1
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/res/xml/accessibilitymenu_service.xml16
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java32
-rw-r--r--packages/SystemUI/docs/demo_mode.md72
-rw-r--r--packages/SystemUI/docs/modern-architecture.pngbin0 -> 23573 bytes
-rw-r--r--packages/SystemUI/docs/status-bar-data-pipeline.md261
-rw-r--r--packages/SystemUI/docs/status-bar-mobile-pipeline.pngbin0 -> 50983 bytes
-rw-r--r--packages/SystemUI/docs/status-bar-pipeline.pngbin0 -> 89119 bytes
-rw-r--r--packages/SystemUI/docs/status-bar.pngbin0 -> 19725 bytes
-rw-r--r--packages/SystemUI/ktfmt_includes.txt2
-rw-r--r--packages/SystemUI/res/values/config.xml4
-rw-r--r--packages/SystemUI/res/values/strings.xml10
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipView.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java67
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java45
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFragment.java38
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt67
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt61
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt115
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt333
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt86
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt94
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/UserCreator.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java38
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt44
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt36
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt189
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt153
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt57
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt27
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt56
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt38
-rw-r--r--services/api/current.txt14
-rw-r--r--services/backup/java/com/android/server/backup/internal/BackupHandler.java9
-rw-r--r--services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java17
-rw-r--r--services/backup/java/com/android/server/backup/transport/BackupTransportClient.java4
-rw-r--r--services/companion/java/com/android/server/companion/virtual/CameraAccessController.java18
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java13
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java204
-rw-r--r--services/core/java/com/android/server/BinaryTransparencyService.java2
-rw-r--r--services/core/java/com/android/server/TelephonyRegistry.java8
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java11
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerLocal.java10
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java75
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java20
-rw-r--r--services/core/java/com/android/server/audio/AudioSystemAdapter.java115
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java19
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java11
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController2.java5
-rw-r--r--services/core/java/com/android/server/display/ScreenOffBrightnessSensorController.java6
-rw-r--r--services/core/java/com/android/server/display/state/DisplayStateController.java2
-rw-r--r--services/core/java/com/android/server/grammaticalinflection/OWNERS4
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java19
-rw-r--r--services/core/java/com/android/server/input/KeyboardBacklightController.java114
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java2
-rw-r--r--services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java62
-rw-r--r--services/core/java/com/android/server/media/MediaRouterService.java10
-rw-r--r--services/core/java/com/android/server/pm/AppStateHelper.java46
-rw-r--r--services/core/java/com/android/server/pm/AppsFilterBase.java39
-rw-r--r--services/core/java/com/android/server/pm/BackgroundDexOptService.java11
-rw-r--r--services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageInternal.java12
-rw-r--r--services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java60
-rw-r--r--services/core/java/com/android/server/security/FileIntegrityService.java20
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java4
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java25
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java20
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java30
-rw-r--r--services/core/java/com/android/server/wm/AppTaskImpl.java2
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java6
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotation.java36
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java2
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicy.java158
-rw-r--r--services/core/java/com/android/server/wm/InsetsPolicy.java32
-rw-r--r--services/core/java/com/android/server/wm/InsetsSourceProvider.java6
-rw-r--r--services/core/java/com/android/server/wm/LetterboxConfiguration.java84
-rw-r--r--services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java120
-rw-r--r--services/core/java/com/android/server/wm/LetterboxUiController.java97
-rw-r--r--services/core/java/com/android/server/wm/RecentTasks.java8
-rw-r--r--services/core/java/com/android/server/wm/RunningTasks.java4
-rw-r--r--services/core/java/com/android/server/wm/Session.java8
-rw-r--r--services/core/java/com/android/server/wm/Task.java70
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java14
-rw-r--r--services/core/java/com/android/server/wm/Transition.java1
-rw-r--r--services/core/java/com/android/server/wm/WallpaperController.java4
-rw-r--r--services/core/java/com/android/server/wm/WallpaperWindowToken.java4
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java18
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerDebugConfig.java3
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java42
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java56
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java24
-rw-r--r--services/core/java/com/android/server/wm/WindowStateAnimator.java4
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java3
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java258
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java306
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java33
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/Owners.java18
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java80
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java15
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java9
-rw-r--r--services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java8
-rw-r--r--services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt4
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/companion/virtual/CameraAccessControllerTest.java7
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/companion/virtual/OWNERS1
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java39
-rw-r--r--services/tests/servicestests/src/com/android/server/display/ScreenOffBrightnessSensorControllerTest.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt82
-rw-r--r--services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml6
-rw-r--r--services/tests/wmtests/Android.bp4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java219
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java10
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationDeviceConfigTests.java109
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java173
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java27
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java5
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java7
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java57
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java4
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java (renamed from services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectorSession.java)48
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java5
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java93
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java6
-rw-r--r--telephony/java/android/telephony/CellBroadcastIdRange.java14
-rw-r--r--telephony/java/android/telephony/SmsManager.java6
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java22
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt4
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt27
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt4
-rw-r--r--tests/FrameworkPerf/AndroidManifest.xml10
-rw-r--r--tests/Input/src/com/android/test/input/InputDeviceTest.java3
-rw-r--r--tests/OneMedia/AndroidManifest.xml4
-rw-r--r--tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeScreenRecordTests.kt3
-rw-r--r--tests/VectorDrawableTest/OWNERS3
-rw-r--r--tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt38
-rw-r--r--tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt240
-rw-r--r--tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt4
361 files changed, 9026 insertions, 2319 deletions
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/BroadcastWaiter.java b/apct-tests/perftests/multiuser/src/android/multiuser/BroadcastWaiter.java
index dcabca476925..727c682b5202 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/BroadcastWaiter.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/BroadcastWaiter.java
@@ -45,7 +45,6 @@ public class BroadcastWaiter implements Closeable {
private final int mTimeoutInSecond;
private final Set<String> mActions;
- private final Set<String> mActionReceivedForUser = new HashSet<>();
private final List<BroadcastReceiver> mBroadcastReceivers = new ArrayList<>();
private final Map<String, Semaphore> mSemaphoresMap = new ConcurrentHashMap<>();
@@ -80,7 +79,6 @@ public class BroadcastWaiter implements Closeable {
final String data = intent.getDataString();
Log.d(mTag, "Received " + action + " for user " + userId
+ (!TextUtils.isEmpty(data) ? " with " + data : ""));
- mActionReceivedForUser.add(action + userId);
getSemaphore(action, userId).release();
}
}
@@ -95,10 +93,6 @@ public class BroadcastWaiter implements Closeable {
mBroadcastReceivers.forEach(mContext::unregisterReceiver);
}
- public boolean hasActionBeenReceivedForUser(String action, int userId) {
- return mActionReceivedForUser.contains(action + userId);
- }
-
private boolean waitActionForUser(String action, int userId) {
Log.d(mTag, "#waitActionForUser(action: " + action + ", userId: " + userId + ")");
@@ -129,7 +123,6 @@ public class BroadcastWaiter implements Closeable {
public String runThenWaitForBroadcasts(int userId, FunctionalUtils.ThrowingRunnable runnable,
String... actions) {
for (String action : actions) {
- mActionReceivedForUser.remove(action + userId);
getSemaphore(action, userId).drainPermits();
}
runnable.run();
@@ -140,9 +133,4 @@ public class BroadcastWaiter implements Closeable {
}
return null;
}
-
- public boolean waitActionForUserIfNotReceivedYet(String action, int userId) {
- return hasActionBeenReceivedForUser(action, userId)
- || waitActionForUser(action, userId);
- }
}
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index b2bd8d7f5d5d..3f9b54cb8578 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -167,7 +167,7 @@ public class UserLifecycleTests {
/** Tests creating a new user. */
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
- public void createUser() {
+ public void createUser() throws RemoteException {
while (mRunner.keepRunning()) {
Log.i(TAG, "Starting timer");
final int userId = createUserNoFlags();
@@ -229,7 +229,7 @@ public class UserLifecycleTests {
* Measures the time until unlock listener is triggered and user is unlocked.
*/
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
- public void startAndUnlockUser() {
+ public void startAndUnlockUser() throws RemoteException {
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
final int userId = createUserNoFlags();
@@ -451,7 +451,7 @@ public class UserLifecycleTests {
/** Tests creating a new profile. */
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
- public void managedProfileCreate() {
+ public void managedProfileCreate() throws RemoteException {
assumeTrue(mHasManagedUserFeature);
while (mRunner.keepRunning()) {
@@ -468,7 +468,7 @@ public class UserLifecycleTests {
/** Tests starting (unlocking) an uninitialized profile. */
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
- public void managedProfileUnlock() {
+ public void managedProfileUnlock() throws RemoteException {
assumeTrue(mHasManagedUserFeature);
while (mRunner.keepRunning()) {
@@ -639,7 +639,7 @@ public class UserLifecycleTests {
// TODO: This is just a POC. Do this properly and add more.
/** Tests starting (unlocking) a newly-created profile using the user-type-pkg-whitelist. */
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
- public void managedProfileUnlock_usingWhitelist() {
+ public void managedProfileUnlock_usingWhitelist() throws RemoteException {
assumeTrue(mHasManagedUserFeature);
final int origMode = getUserTypePackageWhitelistMode();
setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE
@@ -665,7 +665,7 @@ public class UserLifecycleTests {
}
/** Tests starting (unlocking) a newly-created profile NOT using the user-type-pkg-whitelist. */
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
- public void managedProfileUnlock_notUsingWhitelist() {
+ public void managedProfileUnlock_notUsingWhitelist() throws RemoteException {
assumeTrue(mHasManagedUserFeature);
final int origMode = getUserTypePackageWhitelistMode();
setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE);
@@ -908,10 +908,8 @@ public class UserLifecycleTests {
result != null && result.contains("Failed"));
}
- private void removeUser(int userId) {
- if (mBroadcastWaiter.hasActionBeenReceivedForUser(Intent.ACTION_USER_STARTED, userId)) {
- mBroadcastWaiter.waitActionForUserIfNotReceivedYet(Intent.ACTION_MEDIA_MOUNTED, userId);
- }
+ private void removeUser(int userId) throws RemoteException {
+ stopUserAfterWaitingForBroadcastIdle(userId, true);
try {
mUm.removeUser(userId);
final long startTime = System.currentTimeMillis();
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
index fb62920681de..b0da7d1e2870 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
@@ -127,7 +127,7 @@ public class RelayoutPerfTest extends WindowManagerPerfTestBase
final ClientWindowFrames mOutFrames = new ClientWindowFrames();
final MergedConfiguration mOutMergedConfiguration = new MergedConfiguration();
final InsetsState mOutInsetsState = new InsetsState();
- final InsetsSourceControl[] mOutControls = new InsetsSourceControl[0];
+ final InsetsSourceControl.Array mOutControls = new InsetsSourceControl.Array();
final IWindow mWindow;
final View mView;
final WindowManager.LayoutParams mParams;
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
index cc74a5294f9d..b87e42e31da3 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
@@ -86,7 +86,7 @@ public class WindowAddRemovePerfTest extends WindowManagerPerfTestBase
final WindowManager.LayoutParams mLayoutParams = new WindowManager.LayoutParams();
final int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
final InsetsState mOutInsetsState = new InsetsState();
- final InsetsSourceControl[] mOutControls = new InsetsSourceControl[0];
+ final InsetsSourceControl.Array mOutControls = new InsetsSourceControl.Array();
final Rect mOutAttachedFrame = new Rect();
final float[] mOutSizeCompatScale = { 1f };
diff --git a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
index 78214dc27a9f..711caf7feb62 100644
--- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
+++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
@@ -391,6 +391,12 @@ public class PowerExemptionManager {
*/
public static final int REASON_MEDIA_NOTIFICATION_TRANSFER = 325;
+ /**
+ * Package installer.
+ * @hide
+ */
+ public static final int REASON_PACKAGE_INSTALLER = 326;
+
/** @hide The app requests out-out. */
public static final int REASON_OPT_OUT_REQUESTED = 1000;
@@ -472,6 +478,7 @@ public class PowerExemptionManager {
REASON_DISALLOW_APPS_CONTROL,
REASON_ACTIVE_DEVICE_ADMIN,
REASON_MEDIA_NOTIFICATION_TRANSFER,
+ REASON_PACKAGE_INSTALLER,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ReasonCode {}
@@ -839,6 +846,8 @@ public class PowerExemptionManager {
return "REASON_OPT_OUT_REQUESTED";
case REASON_MEDIA_NOTIFICATION_TRANSFER:
return "REASON_MEDIA_NOTIFICATION_TRANSFER";
+ case REASON_PACKAGE_INSTALLER:
+ return "REASON_PACKAGE_INSTALLER";
default:
return "(unknown:" + reasonCode + ")";
}
diff --git a/core/api/current.txt b/core/api/current.txt
index 326a8e781a60..5dd1b3938f40 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -122,6 +122,10 @@ package android {
field public static final String LOADER_USAGE_STATS = "android.permission.LOADER_USAGE_STATS";
field public static final String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE";
field public static final String MANAGE_DEVICE_LOCK_STATE = "android.permission.MANAGE_DEVICE_LOCK_STATE";
+ field public static final String MANAGE_DEVICE_POLICY_ACROSS_USERS = "android.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS";
+ field public static final String MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL = "android.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL";
+ field public static final String MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL = "android.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL";
+ field public static final String MANAGE_DEVICE_POLICY_TIME = "android.permission.MANAGE_DEVICE_POLICY_TIME";
field public static final String MANAGE_DOCUMENTS = "android.permission.MANAGE_DOCUMENTS";
field public static final String MANAGE_EXTERNAL_STORAGE = "android.permission.MANAGE_EXTERNAL_STORAGE";
field public static final String MANAGE_MEDIA = "android.permission.MANAGE_MEDIA";
@@ -7499,9 +7503,9 @@ package android.app.admin {
method @Nullable public String getAlwaysOnVpnPackage(@NonNull android.content.ComponentName);
method @NonNull @WorkerThread public android.os.Bundle getApplicationRestrictions(@Nullable android.content.ComponentName, String);
method @Deprecated @Nullable public String getApplicationRestrictionsManagingPackage(@NonNull android.content.ComponentName);
- method public boolean getAutoTimeEnabled(@NonNull android.content.ComponentName);
+ method @RequiresPermission(anyOf={android.Manifest.permission.SET_TIME, "android.permission.QUERY_ADMIN_POLICY"}, conditional=true) public boolean getAutoTimeEnabled(@NonNull android.content.ComponentName);
method @Deprecated public boolean getAutoTimeRequired();
- method public boolean getAutoTimeZoneEnabled(@NonNull android.content.ComponentName);
+ method @RequiresPermission(anyOf={android.Manifest.permission.SET_TIME_ZONE, "android.permission.QUERY_ADMIN_POLICY"}, conditional=true) public boolean getAutoTimeZoneEnabled(@NonNull android.content.ComponentName);
method @NonNull public java.util.List<android.os.UserHandle> getBindDeviceAdminTargetUsers(@NonNull android.content.ComponentName);
method public boolean getBluetoothContactSharingDisabled(@NonNull android.content.ComponentName);
method public boolean getCameraDisabled(@Nullable android.content.ComponentName);
@@ -7648,9 +7652,9 @@ package android.app.admin {
method public boolean setApplicationHidden(@NonNull android.content.ComponentName, String, boolean);
method @WorkerThread public void setApplicationRestrictions(@Nullable android.content.ComponentName, String, android.os.Bundle);
method @Deprecated public void setApplicationRestrictionsManagingPackage(@NonNull android.content.ComponentName, @Nullable String) throws android.content.pm.PackageManager.NameNotFoundException;
- method public void setAutoTimeEnabled(@NonNull android.content.ComponentName, boolean);
+ method @RequiresPermission(value=android.Manifest.permission.SET_TIME, conditional=true) public void setAutoTimeEnabled(@NonNull android.content.ComponentName, boolean);
method @Deprecated public void setAutoTimeRequired(@NonNull android.content.ComponentName, boolean);
- method public void setAutoTimeZoneEnabled(@NonNull android.content.ComponentName, boolean);
+ method @RequiresPermission(value=android.Manifest.permission.SET_TIME_ZONE, conditional=true) public void setAutoTimeZoneEnabled(@NonNull android.content.ComponentName, boolean);
method public void setBackupServiceEnabled(@NonNull android.content.ComponentName, boolean);
method public void setBluetoothContactSharingDisabled(@NonNull android.content.ComponentName, boolean);
method public void setCameraDisabled(@NonNull android.content.ComponentName, boolean);
@@ -7729,8 +7733,8 @@ package android.app.admin {
method @Deprecated public int setStorageEncryption(@NonNull android.content.ComponentName, boolean);
method public void setSystemSetting(@NonNull android.content.ComponentName, @NonNull String, String);
method public void setSystemUpdatePolicy(@NonNull android.content.ComponentName, android.app.admin.SystemUpdatePolicy);
- method public boolean setTime(@NonNull android.content.ComponentName, long);
- method public boolean setTimeZone(@NonNull android.content.ComponentName, String);
+ method @RequiresPermission(value=android.Manifest.permission.SET_TIME, conditional=true) public boolean setTime(@NonNull android.content.ComponentName, long);
+ method @RequiresPermission(value=android.Manifest.permission.SET_TIME_ZONE, conditional=true) public boolean setTimeZone(@NonNull android.content.ComponentName, String);
method public void setTrustAgentConfiguration(@NonNull android.content.ComponentName, @NonNull android.content.ComponentName, android.os.PersistableBundle);
method public void setUninstallBlocked(@Nullable android.content.ComponentName, String, boolean);
method public void setUsbDataSignalingEnabled(boolean);
@@ -7803,7 +7807,7 @@ package android.app.admin {
field @Deprecated public static final String EXTRA_PROVISIONING_EMAIL_ADDRESS = "android.app.extra.PROVISIONING_EMAIL_ADDRESS";
field public static final String EXTRA_PROVISIONING_IMEI = "android.app.extra.PROVISIONING_IMEI";
field public static final String EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION = "android.app.extra.PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION";
- field public static final String EXTRA_PROVISIONING_KEEP_SCREEN_ON = "android.app.extra.PROVISIONING_KEEP_SCREEN_ON";
+ field @Deprecated public static final String EXTRA_PROVISIONING_KEEP_SCREEN_ON = "android.app.extra.PROVISIONING_KEEP_SCREEN_ON";
field public static final String EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED = "android.app.extra.PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED";
field public static final String EXTRA_PROVISIONING_LOCALE = "android.app.extra.PROVISIONING_LOCALE";
field public static final String EXTRA_PROVISIONING_LOCAL_TIME = "android.app.extra.PROVISIONING_LOCAL_TIME";
@@ -8004,6 +8008,26 @@ package android.app.admin {
field public static final int PACKAGE_POLICY_BLOCKLIST = 1; // 0x1
}
+ public final class PolicyUpdateReason {
+ ctor public PolicyUpdateReason(int);
+ method public int getReasonCode();
+ field public static final int REASON_CONFLICTING_ADMIN_POLICY = 0; // 0x0
+ field public static final int REASON_UNKNOWN = -1; // 0xffffffff
+ }
+
+ public abstract class PolicyUpdatesReceiver extends android.content.BroadcastReceiver {
+ ctor public PolicyUpdatesReceiver();
+ method public void onPolicyChanged(@NonNull android.content.Context, @NonNull String, @NonNull android.os.Bundle, @NonNull android.app.admin.TargetUser, @NonNull android.app.admin.PolicyUpdateReason);
+ method public void onPolicySetResult(@NonNull android.content.Context, @NonNull String, @NonNull android.os.Bundle, @NonNull android.app.admin.TargetUser, int, @Nullable android.app.admin.PolicyUpdateReason);
+ method public final void onReceive(android.content.Context, android.content.Intent);
+ field public static final String ACTION_DEVICE_POLICY_CHANGED = "android.app.admin.action.DEVICE_POLICY_CHANGED";
+ field public static final String ACTION_DEVICE_POLICY_SET_RESULT = "android.app.admin.action.DEVICE_POLICY_SET_RESULT";
+ field public static final String EXTRA_PACKAGE_NAME = "android.app.admin.extra.PACKAGE_NAME";
+ field public static final String EXTRA_PERMISSION_NAME = "android.app.admin.extra.PERMISSION_NAME";
+ field public static final int POLICY_SET_RESULT_FAILURE = -1; // 0xffffffff
+ field public static final int POLICY_SET_RESULT_SUCCESS = 0; // 0x0
+ }
+
public final class PreferentialNetworkServiceConfig implements android.os.Parcelable {
method public int describeContents();
method @NonNull public int[] getExcludedUids();
@@ -8132,6 +8156,12 @@ package android.app.admin {
field public static final int ERROR_UNKNOWN = 1; // 0x1
}
+ public final class TargetUser {
+ field @NonNull public static final android.app.admin.TargetUser GLOBAL;
+ field @NonNull public static final android.app.admin.TargetUser LOCAL_USER;
+ field @NonNull public static final android.app.admin.TargetUser PARENT_USER;
+ }
+
public final class UnsafeStateException extends java.lang.IllegalStateException implements android.os.Parcelable {
method public int describeContents();
method @NonNull public java.util.List<java.lang.Integer> getReasons();
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 2546294f9f5e..447b1136caa0 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -82,6 +82,7 @@ package android.content {
}
public abstract class Context {
+ method @NonNull public android.os.IBinder getIApplicationThreadBinder();
method @NonNull public android.os.UserHandle getUser();
field public static final String PAC_PROXY_SERVICE = "pac_proxy";
field public static final String TEST_NETWORK_SERVICE = "test_network";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 712ac94216d3..e94f5ac1fa40 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -204,6 +204,7 @@ package android {
field public static final String MANAGE_WIFI_COUNTRY_CODE = "android.permission.MANAGE_WIFI_COUNTRY_CODE";
field public static final String MARK_DEVICE_ORGANIZATION_OWNED = "android.permission.MARK_DEVICE_ORGANIZATION_OWNED";
field public static final String MEDIA_RESOURCE_OVERRIDE_PID = "android.permission.MEDIA_RESOURCE_OVERRIDE_PID";
+ field public static final String MIGRATE_HEALTH_CONNECT_DATA = "android.permission.MIGRATE_HEALTH_CONNECT_DATA";
field public static final String MODIFY_APPWIDGET_BIND_PERMISSIONS = "android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS";
field public static final String MODIFY_AUDIO_ROUTING = "android.permission.MODIFY_AUDIO_ROUTING";
field public static final String MODIFY_CELL_BROADCASTS = "android.permission.MODIFY_CELL_BROADCASTS";
@@ -6607,6 +6608,7 @@ package android.media {
public class AudioManager {
method @Deprecated public int abandonAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addAssistantServicesUids(@NonNull int[]);
+ method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE"}) public void addOnDevicesForAttributesChangedListener(@NonNull android.media.AudioAttributes, @NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnDevicesForAttributesChangedListener);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnNonDefaultDevicesForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnNonDefaultDevicesForStrategyChangedListener) throws java.lang.SecurityException;
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDeviceForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener) throws java.lang.SecurityException;
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDevicesForCapturePresetChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDevicesForCapturePresetChangedListener) throws java.lang.SecurityException;
@@ -6647,6 +6649,7 @@ package android.media {
method public void registerVolumeGroupCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.VolumeGroupCallback);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeAssistantServicesUids(@NonNull int[]);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean removeDeviceAsNonDefaultForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull android.media.AudioDeviceAttributes);
+ method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE"}) public void removeOnDevicesForAttributesChangedListener(@NonNull android.media.AudioManager.OnDevicesForAttributesChangedListener);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnNonDefaultDevicesForStrategyChangedListener(@NonNull android.media.AudioManager.OnNonDefaultDevicesForStrategyChangedListener);
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDeviceForStrategyChangedListener(@NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDevicesForCapturePresetChangedListener(@NonNull android.media.AudioManager.OnPreferredDevicesForCapturePresetChangedListener);
@@ -6704,6 +6707,10 @@ package android.media {
field public static final int EVENT_TIMEOUT = 2; // 0x2
}
+ public static interface AudioManager.OnDevicesForAttributesChangedListener {
+ method public void onDevicesForAttributesChanged(@NonNull android.media.AudioAttributes, @NonNull java.util.List<android.media.AudioDeviceAttributes>);
+ }
+
public static interface AudioManager.OnNonDefaultDevicesForStrategyChangedListener {
method public void onNonDefaultDevicesForStrategyChanged(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull java.util.List<android.media.AudioDeviceAttributes>);
}
@@ -12539,6 +12546,19 @@ package android.service.voice {
field public static final int INITIALIZATION_STATUS_UNKNOWN = 100; // 0x64
}
+ public abstract class VisualQueryDetectionService extends android.app.Service implements android.service.voice.SandboxedDetectionServiceBase {
+ ctor public VisualQueryDetectionService();
+ method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
+ method public void onStartDetection(@NonNull android.service.voice.VisualQueryDetectionService.Callback);
+ method public void onStopDetection();
+ method public void onUpdateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, long, @Nullable java.util.function.IntConsumer);
+ field public static final String SERVICE_INTERFACE = "android.service.voice.VisualQueryDetectionService";
+ }
+
+ public static final class VisualQueryDetectionService.Callback {
+ ctor public VisualQueryDetectionService.Callback();
+ }
+
public class VoiceInteractionService extends android.app.Service {
method @NonNull public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, android.service.voice.AlwaysOnHotwordDetector.Callback);
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, @Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, android.service.voice.AlwaysOnHotwordDetector.Callback);
@@ -13212,10 +13232,10 @@ package android.telephony {
}
public final class CellBroadcastIdRange implements android.os.Parcelable {
- ctor public CellBroadcastIdRange(int, int, int, boolean) throws java.lang.IllegalArgumentException;
+ ctor public CellBroadcastIdRange(@IntRange(from=0, to=65535) int, @IntRange(from=0, to=65535) int, int, boolean) throws java.lang.IllegalArgumentException;
method public int describeContents();
- method public int getEndId();
- method public int getStartId();
+ method @IntRange(from=0, to=65535) public int getEndId();
+ method @IntRange(from=0, to=65535) public int getStartId();
method public int getType();
method public boolean isEnabled();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -14157,11 +14177,11 @@ package android.telephony {
field public static final int CDMA_SUBSCRIPTION_NV = 1; // 0x1
field public static final int CDMA_SUBSCRIPTION_RUIM_SIM = 0; // 0x0
field public static final int CDMA_SUBSCRIPTION_UNKNOWN = -1; // 0xffffffff
- field public static final int CELLBROADCAST_RESULT_FAIL_ACTIVATION = 3; // 0x3
- field public static final int CELLBROADCAST_RESULT_FAIL_CONFIG = 2; // 0x2
- field public static final int CELLBROADCAST_RESULT_SUCCESS = 0; // 0x0
- field public static final int CELLBROADCAST_RESULT_UNKNOWN = -1; // 0xffffffff
- field public static final int CELLBROADCAST_RESULT_UNSUPPORTED = 1; // 0x1
+ field public static final int CELL_BROADCAST_RESULT_FAIL_ACTIVATION = 3; // 0x3
+ field public static final int CELL_BROADCAST_RESULT_FAIL_CONFIG = 2; // 0x2
+ field public static final int CELL_BROADCAST_RESULT_SUCCESS = 0; // 0x0
+ field public static final int CELL_BROADCAST_RESULT_UNKNOWN = -1; // 0xffffffff
+ field public static final int CELL_BROADCAST_RESULT_UNSUPPORTED = 1; // 0x1
field public static final int ENABLE_NR_DUAL_CONNECTIVITY_INVALID_STATE = 4; // 0x4
field public static final int ENABLE_NR_DUAL_CONNECTIVITY_NOT_SUPPORTED = 1; // 0x1
field public static final int ENABLE_NR_DUAL_CONNECTIVITY_RADIO_ERROR = 3; // 0x3
diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java
index 871d15ec0b40..51ea04f397d2 100644
--- a/core/java/android/app/ApplicationExitInfo.java
+++ b/core/java/android/app/ApplicationExitInfo.java
@@ -416,6 +416,15 @@ public final class ApplicationExitInfo implements Parcelable {
*/
public static final int SUBREASON_UNDELIVERED_BROADCAST = 26;
+ /**
+ * The process was killed because its associated SDK sandbox process (where it had loaded SDKs)
+ * had died; this would be set only when the reason is {@link #REASON_DEPENDENCY_DIED}.
+ *
+ * For internal use only.
+ * @hide
+ */
+ public static final int SUBREASON_SDK_SANDBOX_DIED = 27;
+
// If there is any OEM code which involves additional app kill reasons, it should
// be categorized in {@link #REASON_OTHER}, with subreason code starting from 1000.
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index 419ffac230f7..e658cb7c302d 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -42,6 +42,8 @@ import java.util.Map;
*/
public abstract class ClientTransactionHandler {
+ private boolean mIsExecutingLocalTransaction;
+
// Schedule phase related logic and handlers.
/** Prepare and schedule transaction for execution. */
@@ -56,9 +58,19 @@ public abstract class ClientTransactionHandler {
*/
@VisibleForTesting
public void executeTransaction(ClientTransaction transaction) {
- transaction.preExecute(this);
- getTransactionExecutor().execute(transaction);
- transaction.recycle();
+ mIsExecutingLocalTransaction = true;
+ try {
+ transaction.preExecute(this);
+ getTransactionExecutor().execute(transaction);
+ } finally {
+ mIsExecutingLocalTransaction = false;
+ transaction.recycle();
+ }
+ }
+
+ /** Returns {@code true} if the current executing ClientTransaction is from local request. */
+ public boolean isExecutingLocalTransaction() {
+ return mIsExecutingLocalTransaction;
}
/**
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 1120257142dc..240dbe1eea24 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2028,6 +2028,13 @@ class ContextImpl extends Context {
}
/** @hide */
+ @NonNull
+ @Override
+ public IBinder getIApplicationThreadBinder() {
+ return getIApplicationThread().asBinder();
+ }
+
+ /** @hide */
@Override
public Handler getMainThreadHandler() {
return mMainThread.getHandler();
@@ -3039,7 +3046,8 @@ class ContextImpl extends Context {
public void updateDeviceId(int updatedDeviceId) {
if (!isValidDeviceId(updatedDeviceId)) {
throw new IllegalArgumentException(
- "Not a valid ID of the default device or any virtual device: " + mDeviceId);
+ "Not a valid ID of the default device or any virtual device: "
+ + updatedDeviceId);
}
if (mIsExplicitDeviceId) {
throw new UnsupportedOperationException(
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index e729e7d8f9be..209b112ec9ed 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -16,6 +16,9 @@
package android.app.admin;
+import static android.Manifest.permission.QUERY_ADMIN_POLICY;
+import static android.Manifest.permission.SET_TIME;
+import static android.Manifest.permission.SET_TIME_ZONE;
import static android.content.Intent.LOCAL_FLAG_FROM_SYSTEM;
import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
@@ -3311,13 +3314,16 @@ public class DevicePolicyManager {
* A {@code boolean} flag that indicates whether the screen should be on throughout the
* provisioning flow.
*
- * <p>The default value is {@code false}.
- *
* <p>This extra can either be passed as an extra to the {@link
* #ACTION_PROVISION_MANAGED_PROFILE} intent, or it can be returned by the
* admin app when performing the admin-integrated provisioning flow as a result of the
* {@link #ACTION_GET_PROVISIONING_MODE} activity.
+ *
+ * @deprecated from {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, the flag wouldn't
+ * be functional. The screen is kept on throughout the provisioning flow.
*/
+
+ @Deprecated
public static final String EXTRA_PROVISIONING_KEEP_SCREEN_ON =
"android.app.extra.PROVISIONING_KEEP_SCREEN_ON";
@@ -8466,8 +8472,10 @@ public class DevicePolicyManager {
}
/**
- * Called by a device owner, a profile owner for the primary user or a profile
- * owner of an organization-owned managed profile to turn auto time on and off.
+ * Called by a device owner, a profile owner for the primary user, a profile
+ * owner of an organization-owned managed profile or, starting from Android
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, holders of the permission
+ * {@link android.Manifest.permission#SET_TIME} to turn auto time on and off.
* Callers are recommended to use {@link UserManager#DISALLOW_CONFIG_DATE_TIME}
* to prevent the user from changing this setting.
* <p>
@@ -8478,8 +8486,10 @@ public class DevicePolicyManager {
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param enabled Whether time should be obtained automatically from the network or not.
* @throws SecurityException if caller is not a device owner, a profile owner for the
- * primary user, or a profile owner of an organization-owned managed profile.
+ * primary user, or a profile owner of an organization-owned managed profile or a holder of the
+ * permission {@link android.Manifest.permission#SET_TIME}.
*/
+ @RequiresPermission(value = SET_TIME, conditional = true)
public void setAutoTimeEnabled(@NonNull ComponentName admin, boolean enabled) {
if (mService != null) {
try {
@@ -8491,10 +8501,18 @@ public class DevicePolicyManager {
}
/**
+ * Returns true if auto time is enabled on the device.
+ *
+ * <p> Starting from Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, callers
+ * are also able to call this method if they hold the permission
+ *{@link android.Manifest.permission#SET_TIME}.
+ *
* @return true if auto time is enabled on the device.
- * @throws SecurityException if caller is not a device owner, a profile owner for the
- * primary user, or a profile owner of an organization-owned managed profile.
+ * @throws SecurityException if the caller is not a device owner, a profile
+ * owner for the primary user, or a profile owner of an organization-owned managed profile or a
+ * holder of the permission {@link android.Manifest.permission#SET_TIME}.
*/
+ @RequiresPermission(anyOf = {SET_TIME, QUERY_ADMIN_POLICY}, conditional = true)
public boolean getAutoTimeEnabled(@NonNull ComponentName admin) {
if (mService != null) {
try {
@@ -8507,8 +8525,10 @@ public class DevicePolicyManager {
}
/**
- * Called by a device owner, a profile owner for the primary user or a profile
- * owner of an organization-owned managed profile to turn auto time zone on and off.
+ * Called by a device owner, a profile owner for the primary user, a profile
+ * owner of an organization-owned managed profile or, starting from Android
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, holders of the permission
+ * {@link android.Manifest.permission#SET_TIME} to turn auto time zone on and off.
* Callers are recommended to use {@link UserManager#DISALLOW_CONFIG_DATE_TIME}
* to prevent the user from changing this setting.
* <p>
@@ -8519,8 +8539,10 @@ public class DevicePolicyManager {
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param enabled Whether time zone should be obtained automatically from the network or not.
* @throws SecurityException if caller is not a device owner, a profile owner for the
- * primary user, or a profile owner of an organization-owned managed profile.
+ * primary user, or a profile owner of an organization-owned managed profile or a holder of the
+ * permission {@link android.Manifest.permission#SET_TIME_ZONE}.
*/
+ @RequiresPermission(value = SET_TIME_ZONE, conditional = true)
public void setAutoTimeZoneEnabled(@NonNull ComponentName admin, boolean enabled) {
throwIfParentInstance("setAutoTimeZone");
if (mService != null) {
@@ -8533,10 +8555,18 @@ public class DevicePolicyManager {
}
/**
+ * Returns true if auto time zone is enabled on the device.
+ *
+ * <p> Starting from Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, callers
+ * are also able to call this method if they hold the permission
+ *{@link android.Manifest.permission#SET_TIME}.
+ *
* @return true if auto time zone is enabled on the device.
- * @throws SecurityException if caller is not a device owner, a profile owner for the
- * primary user, or a profile owner of an organization-owned managed profile.
+ * @throws SecurityException if the caller is not a device owner, a profile
+ * owner for the primary user, or a profile owner of an organization-owned managed profile or a
+ * holder of the permission {@link android.Manifest.permission#SET_TIME_ZONE}.
*/
+ @RequiresPermission(anyOf = {SET_TIME_ZONE, QUERY_ADMIN_POLICY}, conditional = true)
public boolean getAutoTimeZoneEnabled(@NonNull ComponentName admin) {
throwIfParentInstance("getAutoTimeZone");
if (mService != null) {
@@ -11875,17 +11905,21 @@ public class DevicePolicyManager {
}
/**
- * Called by a device owner or a profile owner of an organization-owned managed
- * profile to set the system wall clock time. This only takes effect if called when
- * {@link android.provider.Settings.Global#AUTO_TIME} is 0, otherwise {@code false}
- * will be returned.
+ * Called by a device owner, a profile owner of an organization-owned managed
+ * profile or, starting from Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * holders of the permission {@link android.Manifest.permission#SET_TIME} to set the system wall
+ * clock time. This only takes effect if called when
+ * {@link android.provider.Settings.Global#AUTO_TIME} is 0, otherwise {@code false} will be
+ * returned.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with
* @param millis time in milliseconds since the Epoch
* @return {@code true} if set time succeeded, {@code false} otherwise.
* @throws SecurityException if {@code admin} is not a device owner or a profile owner
- * of an organization-owned managed profile.
+ * of an organization-owned managed profile or a holder of the permission
+ * {@link android.Manifest.permission#SET_TIME}.
*/
+ @RequiresPermission(value = SET_TIME, conditional = true)
public boolean setTime(@NonNull ComponentName admin, long millis) {
throwIfParentInstance("setTime");
if (mService != null) {
@@ -11899,10 +11933,12 @@ public class DevicePolicyManager {
}
/**
- * Called by a device owner or a profile owner of an organization-owned managed
- * profile to set the system's persistent default time zone. This only takes
- * effect if called when {@link android.provider.Settings.Global#AUTO_TIME_ZONE}
- * is 0, otherwise {@code false} will be returned.
+ * Called by a device owner, a profile owner of an organization-owned managed
+ * profile or, starting from Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * holders of the permission {@link android.Manifest.permission#SET_TIME_ZONE} to set the
+ * system's persistent default time zone. This only take effect if called when
+ * {@link android.provider.Settings.Global#AUTO_TIME_ZONE} is 0, otherwise {@code false} will be
+ * returned.
*
* @see android.app.AlarmManager#setTimeZone(String)
* @param admin Which {@link DeviceAdminReceiver} this request is associated with
@@ -11910,8 +11946,10 @@ public class DevicePolicyManager {
* {@link java.util.TimeZone#getAvailableIDs}
* @return {@code true} if set timezone succeeded, {@code false} otherwise.
* @throws SecurityException if {@code admin} is not a device owner or a profile owner
- * of an organization-owned managed profile.
+ * of an organization-owned managed profile or a holder of the permissions
+ * {@link android.Manifest.permission#SET_TIME_ZONE}.
*/
+ @RequiresPermission(value = SET_TIME_ZONE, conditional = true)
public boolean setTimeZone(@NonNull ComponentName admin, String timeZone) {
throwIfParentInstance("setTimeZone");
if (mService != null) {
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index 840f3a3c6bb8..3a61ca1fbf93 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -271,6 +271,31 @@ public abstract class DevicePolicyManagerInternal {
public abstract void resetOp(int op, String packageName, @UserIdInt int userId);
/**
+ * Checks if the calling process has been granted permission to apply a device policy on a
+ * specific user.
+ *
+ * The given permission will be checked along with its associated cross-user permission, if it
+ * exists and the target user is different to the calling user.
+ *
+ * @param permission The name of the permission being checked.
+ * @param targetUserId The userId of the user which the caller needs permission to act on.
+ * @throws SecurityException If the calling process has not been granted the permission.
+ */
+ public abstract void enforcePermission(String permission, int targetUserId);
+
+ /**
+ * Return whether the calling process has been granted permission to apply a device policy on
+ * a specific user.
+ *
+ * The given permission will be checked along with its associated cross-user
+ * permission, if it exists and the target user is different to the calling user.
+ *
+ * @param permission The name of the permission being checked.
+ * @param targetUserId The userId of the user which the caller needs permission to act on.
+ */
+ public abstract boolean hasPermission(String permission, int targetUserId);
+
+ /**
* Returns whether new "turn off work" behavior is enabled via feature flag.
*/
public abstract boolean isKeepProfilesRunningEnabled();
diff --git a/core/java/android/app/admin/PolicyUpdateReason.java b/core/java/android/app/admin/PolicyUpdateReason.java
new file mode 100644
index 000000000000..97d282dbc8d7
--- /dev/null
+++ b/core/java/android/app/admin/PolicyUpdateReason.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.admin;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Class containing the reason a policy (set from {@link DevicePolicyManager}) hasn't been enforced
+ * (passed in to {@link PolicyUpdatesReceiver#onPolicySetResult}) or has changed (passed in to
+ * {@link PolicyUpdatesReceiver#onPolicyChanged}).
+ */
+public final class PolicyUpdateReason {
+
+ /**
+ * Reason code to indicate that the policy has not been enforced or has changed for an unknown
+ * reason.
+ */
+ public static final int REASON_UNKNOWN = -1;
+
+ /**
+ * Reason code to indicate that the policy has not been enforced or has changed because another
+ * admin has set a conflicting policy on the device.
+ */
+ public static final int REASON_CONFLICTING_ADMIN_POLICY = 0;
+
+ /**
+ * Reason codes for {@link #getReasonCode()}.
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "REASON_" }, value = {
+ REASON_UNKNOWN,
+ REASON_CONFLICTING_ADMIN_POLICY,
+ })
+ public @interface ReasonCode {}
+
+ private final int mReasonCode;
+
+ /**
+ * Constructor for {@code PolicyUpdateReason} that takes in a reason code describing why the
+ * policy has changed.
+ *
+ * @param reasonCode Describes why the policy has changed.
+ */
+ public PolicyUpdateReason(@ReasonCode int reasonCode) {
+ this.mReasonCode = reasonCode;
+ }
+
+ /**
+ * Returns reason code for why a policy hasn't been applied or has changed.
+ */
+ @ReasonCode
+ public int getReasonCode() {
+ return mReasonCode;
+ }
+}
diff --git a/core/java/android/app/admin/PolicyUpdatesReceiver.java b/core/java/android/app/admin/PolicyUpdatesReceiver.java
new file mode 100644
index 000000000000..ff30a5f8a037
--- /dev/null
+++ b/core/java/android/app/admin/PolicyUpdatesReceiver.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.admin;
+
+import android.annotation.BroadcastBehavior;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SuppressLint;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+// TODO(b/261432333): Add more detailed javadocs on using DeviceAdminService.
+/**
+ * Base class for implementing a policy update receiver. This class provides a convenience for
+ * interpreting the raw intent actions ({@link #ACTION_DEVICE_POLICY_SET_RESULT} and
+ * {@link #ACTION_DEVICE_POLICY_CHANGED}) that are sent by the system.
+ *
+ * <p>The callback methods happen on the main thread of the process. Thus, long-running
+ * operations must be done on another thread.
+ *
+ * <p>When publishing your {@code PolicyUpdatesReceiver} subclass as a receiver, it must
+ * require the {@link android.Manifest.permission#BIND_DEVICE_ADMIN} permission.
+ */
+public abstract class PolicyUpdatesReceiver extends BroadcastReceiver {
+ private static String TAG = "PolicyUpdatesReceiver";
+
+ /**
+ * Result code passed in to {@link #onPolicySetResult} to indicate that the policy has been
+ * set successfully.
+ */
+ public static final int POLICY_SET_RESULT_SUCCESS = 0;
+
+ /**
+ * Result code passed in to {@link #onPolicySetResult} to indicate that the policy has NOT been
+ * set, a {@link PolicyUpdateReason} will be passed in to {@link #onPolicySetResult} to indicate
+ * the reason.
+ */
+ public static final int POLICY_SET_RESULT_FAILURE = -1;
+
+ /**
+ * Result codes passed in to {@link #onPolicySetResult}.
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "POLICY_SET_RESULT_" }, value = {
+ POLICY_SET_RESULT_SUCCESS,
+ POLICY_SET_RESULT_FAILURE,
+ })
+ public @interface ResultCode {}
+
+ /**
+ * Action for a broadcast sent to admins to communicate back the result of setting a policy in
+ * {@link DevicePolicyManager}.
+ *
+ * <p>Admins wishing to receive these updates (via {@link #onPolicySetResult}) should include
+ * this action in the intent filter for their receiver in the manifest, the receiver
+ * must be protected by {@link android.Manifest.permission#BIND_DEVICE_ADMIN} to ensure that
+ * only the system can send updates.
+ *
+ * <p>Admins shouldn't implement {@link #onReceive} and should instead implement
+ * {@link #onPolicySetResult}.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ @BroadcastBehavior(explicitOnly = true)
+ public static final String ACTION_DEVICE_POLICY_SET_RESULT =
+ "android.app.admin.action.DEVICE_POLICY_SET_RESULT";
+
+ /**
+ * Action for a broadcast sent to admins to communicate back a change in a policy they have
+ * previously set.
+ *
+ * <p>Admins wishing to receive these updates should include this action in the intent filter
+ * for their receiver in the manifest, the receiver must be protected by
+ * {@link android.Manifest.permission#BIND_DEVICE_ADMIN} to ensure that only the system can
+ * send updates.
+ *
+ * <p>Admins shouldn't implement {@link #onReceive} and should instead implement
+ * {@link #onPolicyChanged}.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ @BroadcastBehavior(explicitOnly = true)
+ public static final String ACTION_DEVICE_POLICY_CHANGED =
+ "android.app.admin.action.DEVICE_POLICY_CHANGED";
+
+ // TODO(b/264510719): Remove once API linter is fixed
+ @SuppressLint("ActionValue")
+ /**
+ * A string extra holding the package name the policy applies to, (see
+ * {@link PolicyUpdatesReceiver#onPolicyChanged} and
+ * {@link PolicyUpdatesReceiver#onPolicySetResult})
+ */
+ public static final String EXTRA_PACKAGE_NAME =
+ "android.app.admin.extra.PACKAGE_NAME";
+
+ // TODO(b/264510719): Remove once API linter is fixed
+ @SuppressLint("ActionValue")
+ /**
+ * A string extra holding the permission name the policy applies to, (see
+ * {@link PolicyUpdatesReceiver#onPolicyChanged} and
+ * {@link PolicyUpdatesReceiver#onPolicySetResult})
+ */
+ public static final String EXTRA_PERMISSION_NAME =
+ "android.app.admin.extra.PERMISSION_NAME";
+
+ /**
+ * @hide
+ */
+ public static final String EXTRA_POLICY_CHANGED_KEY =
+ "android.app.admin.extra.POLICY_CHANGED_KEY";
+
+ /**
+ * @hide
+ */
+ public static final String EXTRA_POLICY_KEY = "android.app.admin.extra.POLICY_KEY";
+
+ /**
+ * @hide
+ */
+ public static final String EXTRA_POLICY_BUNDLE_KEY =
+ "android.app.admin.extra.POLICY_BUNDLE_KEY";
+
+ /**
+ * @hide
+ */
+ public static final String EXTRA_POLICY_SET_RESULT_KEY =
+ "android.app.admin.extra.POLICY_SET_RESULT_KEY";
+
+ /**
+ * @hide
+ */
+ public static final String EXTRA_POLICY_UPDATE_REASON_KEY =
+ "android.app.admin.extra.POLICY_UPDATE_REASON_KEY";
+
+ /**
+ * @hide
+ */
+ public static final String EXTRA_POLICY_TARGET_USER_ID =
+ "android.app.admin.extra.POLICY_TARGET_USER_ID";
+
+ /**
+ * Intercept standard policy update broadcasts. Implementations should not override this
+ * method and rely on the callbacks instead.
+ *
+ * @hide
+ */
+ @Override
+ public final void onReceive(Context context, Intent intent) {
+ Objects.requireNonNull(intent.getAction());
+ switch (intent.getAction()) {
+ case ACTION_DEVICE_POLICY_SET_RESULT:
+ Log.i(TAG, "Received ACTION_DEVICE_POLICY_SET_RESULT");
+ onPolicySetResult(context, getPolicyKey(intent), getPolicyExtraBundle(intent),
+ getTargetUser(intent), getPolicyResult(intent), getFailureReason(intent));
+ break;
+ case ACTION_DEVICE_POLICY_CHANGED:
+ Log.i(TAG, "Received ACTION_DEVICE_POLICY_CHANGED");
+ onPolicyChanged(context, getPolicyKey(intent), getPolicyExtraBundle(intent),
+ getTargetUser(intent), getPolicyChangedReason(intent));
+ break;
+ default:
+ Log.e(TAG, "Unknown action received: " + intent.getAction());
+ }
+ }
+
+ /**
+ * @hide
+ */
+ static String getPolicyKey(Intent intent) {
+ if (!intent.hasExtra(EXTRA_POLICY_KEY)) {
+ throw new IllegalArgumentException("PolicyKey has to be provided.");
+ }
+ return intent.getStringExtra(EXTRA_POLICY_KEY);
+ }
+
+ /**
+ * @hide
+ */
+ @ResultCode
+ static int getPolicyResult(Intent intent) {
+ if (!intent.hasExtra(EXTRA_POLICY_SET_RESULT_KEY)) {
+ throw new IllegalArgumentException("Result has to be provided.");
+ }
+ return intent.getIntExtra(EXTRA_POLICY_SET_RESULT_KEY, POLICY_SET_RESULT_FAILURE);
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ static Bundle getPolicyExtraBundle(Intent intent) {
+ Bundle bundle = intent.getBundleExtra(EXTRA_POLICY_BUNDLE_KEY);
+ return bundle == null ? new Bundle() : bundle;
+ }
+
+ /**
+ * @hide
+ */
+ @Nullable
+ static PolicyUpdateReason getFailureReason(Intent intent) {
+ if (getPolicyResult(intent) != POLICY_SET_RESULT_FAILURE) {
+ return null;
+ }
+ return getPolicyChangedReason(intent);
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ static PolicyUpdateReason getPolicyChangedReason(Intent intent) {
+ int reasonCode = intent.getIntExtra(
+ EXTRA_POLICY_UPDATE_REASON_KEY, PolicyUpdateReason.REASON_UNKNOWN);
+ return new PolicyUpdateReason(reasonCode);
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ static TargetUser getTargetUser(Intent intent) {
+ if (!intent.hasExtra(EXTRA_POLICY_TARGET_USER_ID)) {
+ throw new IllegalArgumentException("TargetUser has to be provided.");
+ }
+ int targetUserId = intent.getIntExtra(
+ EXTRA_POLICY_TARGET_USER_ID, TargetUser.LOCAL_USER_ID);
+ return new TargetUser(targetUserId);
+ }
+
+ // TODO(b/260847505): Add javadocs to explain which DPM APIs are supported
+ /**
+ * Callback triggered after an admin has set a policy using one of the APIs in
+ * {@link DevicePolicyManager} to notify the admin whether it has been successful or not.
+ *
+ * <p>Admins wishing to receive this callback should include
+ * {@link PolicyUpdatesReceiver#ACTION_DEVICE_POLICY_SET_RESULT} in the intent filter for their
+ * receiver in the manifest, the receiver must be protected by
+ * {@link android.Manifest.permission#BIND_DEVICE_ADMIN} to ensure that only the system can
+ * send updates.
+ *
+ * @param context the running context as per {@link #onReceive}
+ * @param policyKey Key to identify which policy this callback relates to.
+ * @param additionalPolicyParams Bundle containing additional params that may be required to
+ * identify some of the policy
+ * (e.g. {@link PolicyUpdatesReceiver#EXTRA_PACKAGE_NAME}
+ * and {@link PolicyUpdatesReceiver#EXTRA_PERMISSION_NAME}).
+ * Each policy will document the required additional params if
+ * needed.
+ * @param targetUser The {@link TargetUser} which this policy relates to.
+ * @param result Indicates whether the policy has been set successfully,
+ * (see {@link PolicyUpdatesReceiver#POLICY_SET_RESULT_SUCCESS} and
+ * {@link PolicyUpdatesReceiver#POLICY_SET_RESULT_FAILURE}).
+ * @param reason Indicates the reason the policy failed to apply, {@code null} if the policy was
+ * applied successfully.
+ */
+ public void onPolicySetResult(
+ @NonNull Context context,
+ @NonNull String policyKey,
+ @NonNull Bundle additionalPolicyParams,
+ @NonNull TargetUser targetUser,
+ @ResultCode int result,
+ @Nullable PolicyUpdateReason reason) {}
+
+ // TODO(b/260847505): Add javadocs to explain which DPM APIs are supported
+ // TODO(b/261430877): Add javadocs to explain when will this get triggered
+ /**
+ * Callback triggered when a policy previously set by the admin has changed.
+ *
+ * <p>Admins wishing to receive this callback should include
+ * {@link PolicyUpdatesReceiver#ACTION_DEVICE_POLICY_CHANGED} in the intent filter for their
+ * receiver in the manifest, the receiver must be protected by
+ * {@link android.Manifest.permission#BIND_DEVICE_ADMIN} to ensure that only the system can
+ * send updates.
+ *
+ * @param context the running context as per {@link #onReceive}
+ * @param policyKey Key to identify which policy this callback relates to.
+ * @param additionalPolicyParams Bundle containing additional params that may be required to
+ * identify some of the policy
+ * (e.g. {@link PolicyUpdatesReceiver#EXTRA_PACKAGE_NAME}
+ * and {@link PolicyUpdatesReceiver#EXTRA_PERMISSION_NAME}).
+ * Each policy will document the required additional params if
+ * needed.
+ * @param targetUser The {@link TargetUser} which this policy relates to.
+ * @param reason Indicates the reason the policy value has changed.
+ */
+ public void onPolicyChanged(
+ @NonNull Context context,
+ @NonNull String policyKey,
+ @NonNull Bundle additionalPolicyParams,
+ @NonNull TargetUser targetUser,
+ @NonNull PolicyUpdateReason reason) {}
+}
diff --git a/core/java/android/app/admin/TargetUser.java b/core/java/android/app/admin/TargetUser.java
new file mode 100644
index 000000000000..acbac29dabe6
--- /dev/null
+++ b/core/java/android/app/admin/TargetUser.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.admin;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.util.Objects;
+
+/**
+ * Class representing the target user of a policy set by an admin
+ * (set from {@link DevicePolicyManager}), this is passed in to
+ * {@link PolicyUpdatesReceiver#onPolicySetResult} and
+ * {@link PolicyUpdatesReceiver#onPolicyChanged}.
+ */
+public final class TargetUser {
+ /**
+ * @hide
+ */
+ public static final int LOCAL_USER_ID = -1;
+
+ /**
+ * @hide
+ */
+ public static final int PARENT_USER_ID = -2;
+
+ /**
+ * @hide
+ */
+ public static final int GLOBAL_USER_ID = -3;
+
+ /**
+ * Indicates that the policy relates to the user the admin is installed on.
+ */
+ @NonNull
+ public static final TargetUser LOCAL_USER = new TargetUser(LOCAL_USER_ID);
+
+ /**
+ * For admins of profiles, this indicates that the policy relates to the parent profile.
+ */
+ @NonNull
+ public static final TargetUser PARENT_USER = new TargetUser(PARENT_USER_ID);
+
+ /**
+ * This indicates the policy is a global policy.
+ */
+ @NonNull
+ public static final TargetUser GLOBAL = new TargetUser(GLOBAL_USER_ID);
+
+ private final int mUserId;
+
+ /**
+ * @hide
+ */
+ public TargetUser(int userId) {
+ mUserId = userId;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ TargetUser other = (TargetUser) o;
+ return mUserId == other.mUserId;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mUserId);
+ }
+}
diff --git a/core/java/android/app/servertransaction/ActivityRelaunchItem.java b/core/java/android/app/servertransaction/ActivityRelaunchItem.java
index c09461c1fb52..b26dac712609 100644
--- a/core/java/android/app/servertransaction/ActivityRelaunchItem.java
+++ b/core/java/android/app/servertransaction/ActivityRelaunchItem.java
@@ -57,7 +57,10 @@ public class ActivityRelaunchItem extends ActivityTransactionItem {
@Override
public void preExecute(ClientTransactionHandler client, IBinder token) {
- CompatibilityInfo.applyOverrideScaleIfNeeded(mConfig);
+ // The local config is already scaled so only apply if this item is from server side.
+ if (!client.isExecutingLocalTransaction()) {
+ CompatibilityInfo.applyOverrideScaleIfNeeded(mConfig);
+ }
mActivityClientRecord = client.prepareRelaunchActivity(token, mPendingResults,
mPendingNewIntents, mConfigChanges, mConfig, mPreserveWindow);
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 7d7232e36d72..12a2cae4c5c8 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2346,7 +2346,8 @@ public abstract class Context {
@SystemApi
public void sendBroadcastMultiplePermissions(@NonNull Intent intent,
@NonNull String[] receiverPermissions, @Nullable BroadcastOptions options) {
- sendBroadcastMultiplePermissions(intent, receiverPermissions, options.toBundle());
+ sendBroadcastMultiplePermissions(intent, receiverPermissions,
+ (options == null ? null : options.toBundle()));
}
/**
@@ -7491,6 +7492,18 @@ public abstract class Context {
}
/**
+ * Get the binder object associated with the IApplicationThread of this Context.
+ *
+ * This can be used by a mainline module to uniquely identify a specific app process.
+ * @hide
+ */
+ @NonNull
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public IBinder getIApplicationThreadBinder() {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
* @hide
*/
public Handler getMainThreadHandler() {
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 0a32dd78092f..0dc4adc79f30 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -1277,6 +1277,14 @@ public class ContextWrapper extends Context {
* @hide
*/
@Override
+ public IBinder getIApplicationThreadBinder() {
+ return mBase.getIApplicationThreadBinder();
+ }
+
+ /**
+ * @hide
+ */
+ @Override
public Handler getMainThreadHandler() {
return mBase.getMainThreadHandler();
}
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index c5ebf34c40ca..a9f55bc1ea4c 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1066,6 +1066,19 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
public @interface SizeChangesSupportMode {}
/**
+ * This change id enables compat policy that ignores app requested orientation in
+ * response to an app calling {@link android.app.Activity#setRequestedOrientation}. See
+ * com.android.server.wm.LetterboxUiController#shouldIgnoreRequestedOrientation for
+ * details.
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ public static final long OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION =
+ 254631730L; // buganizer id
+
+ /**
* This change id forces the packages it is applied to never have Display API sandboxing
* applied for a letterbox or SCM activity. The Display APIs will continue to provide
* DisplayArea bounds.
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 5291d2b73891..c716f319103a 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -17,12 +17,7 @@
package android.hardware;
import static android.system.OsConstants.EACCES;
-import static android.system.OsConstants.EBUSY;
-import static android.system.OsConstants.EINVAL;
import static android.system.OsConstants.ENODEV;
-import static android.system.OsConstants.ENOSYS;
-import static android.system.OsConstants.EOPNOTSUPP;
-import static android.system.OsConstants.EUSERS;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index ed1e9e5f6228..5b6e288d9da9 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -59,7 +59,6 @@ import android.util.Log;
import android.util.Size;
import android.view.Display;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import java.lang.ref.WeakReference;
diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
index 6f77d12cc463..3fc44f8f84ac 100644
--- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
+++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
@@ -2429,7 +2429,7 @@ public final class MandatoryStreamCombination {
long minFrameDuration = mStreamConfigMap.getOutputMinFrameDuration(
android.media.MediaRecorder.class, sz);
// Give some margin for rounding error
- if (minFrameDuration > (1e9 / 30.1)) {
+ if (minFrameDuration < (1e9 / 29.9)) {
Log.i(TAG, "External camera " + mCameraId + " has max video size:" + sz);
return sz;
}
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 6314cabd298d..222cf080379c 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -22,6 +22,8 @@ import android.hardware.input.KeyboardLayout;
import android.hardware.input.IInputDevicesChangedListener;
import android.hardware.input.IInputDeviceBatteryListener;
import android.hardware.input.IInputDeviceBatteryState;
+import android.hardware.input.IKeyboardBacklightListener;
+import android.hardware.input.IKeyboardBacklightState;
import android.hardware.input.ITabletModeChangedListener;
import android.hardware.input.TouchCalibration;
import android.os.CombinedVibration;
@@ -221,4 +223,14 @@ interface IInputManager {
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ "android.Manifest.permission.MONITOR_INPUT)")
void pilferPointers(IBinder inputChannelToken);
+
+ @EnforcePermission("MONITOR_KEYBOARD_BACKLIGHT")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.MONITOR_KEYBOARD_BACKLIGHT)")
+ void registerKeyboardBacklightListener(IKeyboardBacklightListener listener);
+
+ @EnforcePermission("MONITOR_KEYBOARD_BACKLIGHT")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.MONITOR_KEYBOARD_BACKLIGHT)")
+ void unregisterKeyboardBacklightListener(IKeyboardBacklightListener listener);
}
diff --git a/core/java/android/hardware/input/IKeyboardBacklightListener.aidl b/core/java/android/hardware/input/IKeyboardBacklightListener.aidl
new file mode 100644
index 000000000000..2c1f2520350d
--- /dev/null
+++ b/core/java/android/hardware/input/IKeyboardBacklightListener.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import android.hardware.input.IKeyboardBacklightState;
+
+/** @hide */
+oneway interface IKeyboardBacklightListener {
+
+ /**
+ * Called when the keyboard backlight brightness is changed.
+ */
+ void onBrightnessChanged(int deviceId, in IKeyboardBacklightState state, boolean isTriggeredByKeyPress);
+}
diff --git a/core/java/android/hardware/input/IKeyboardBacklightState.aidl b/core/java/android/hardware/input/IKeyboardBacklightState.aidl
new file mode 100644
index 000000000000..b04aa66d3e81
--- /dev/null
+++ b/core/java/android/hardware/input/IKeyboardBacklightState.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+/** @hide */
+@JavaDerive(equals=true)
+parcelable IKeyboardBacklightState {
+ /** Current brightness level of the keyboard backlight in the range [0, maxBrightnessLevel]*/
+ int brightnessLevel;
+
+ /** Maximum brightness level of keyboard backlight */
+ int maxBrightnessLevel;
+} \ No newline at end of file
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 655e5981971f..fb201cfbbe38 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -119,6 +119,12 @@ public final class InputManager {
@GuardedBy("mBatteryListenersLock")
private IInputDeviceBatteryListener mInputDeviceBatteryListener;
+ private final Object mKeyboardBacklightListenerLock = new Object();
+ @GuardedBy("mKeyboardBacklightListenerLock")
+ private List<KeyboardBacklightListenerDelegate> mKeyboardBacklightListeners;
+ @GuardedBy("mKeyboardBacklightListenerLock")
+ private IKeyboardBacklightListener mKeyboardBacklightListener;
+
private InputDeviceSensorManager mInputDeviceSensorManager;
/**
* Broadcast Action: Query available keyboard layouts.
@@ -2281,6 +2287,74 @@ public final class InputManager {
}
/**
+ * Registers a Keyboard backlight change listener to be notified about {@link
+ * KeyboardBacklightState} changes for connected keyboard devices.
+ *
+ * @param executor an executor on which the callback will be called
+ * @param listener the {@link KeyboardBacklightListener}
+ * @hide
+ * @see #unregisterKeyboardBacklightListener(KeyboardBacklightListener)
+ * @throws IllegalArgumentException if {@code listener} has already been registered previously.
+ * @throws NullPointerException if {@code listener} or {@code executor} is null.
+ */
+ @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_BACKLIGHT)
+ public void registerKeyboardBacklightListener(@NonNull Executor executor,
+ @NonNull KeyboardBacklightListener listener) throws IllegalArgumentException {
+ Objects.requireNonNull(executor, "executor should not be null");
+ Objects.requireNonNull(listener, "listener should not be null");
+
+ synchronized (mKeyboardBacklightListenerLock) {
+ if (mKeyboardBacklightListener == null) {
+ mKeyboardBacklightListeners = new ArrayList<>();
+ mKeyboardBacklightListener = new LocalKeyboardBacklightListener();
+
+ try {
+ mIm.registerKeyboardBacklightListener(mKeyboardBacklightListener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ for (KeyboardBacklightListenerDelegate delegate : mKeyboardBacklightListeners) {
+ if (delegate.mListener == listener) {
+ throw new IllegalArgumentException("Listener has already been registered!");
+ }
+ }
+ KeyboardBacklightListenerDelegate delegate =
+ new KeyboardBacklightListenerDelegate(listener, executor);
+ mKeyboardBacklightListeners.add(delegate);
+ }
+ }
+
+ /**
+ * Unregisters a previously added Keyboard backlight change listener.
+ *
+ * @param listener the {@link KeyboardBacklightListener}
+ * @see #registerKeyboardBacklightListener(Executor, KeyboardBacklightListener)
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_BACKLIGHT)
+ public void unregisterKeyboardBacklightListener(
+ @NonNull KeyboardBacklightListener listener) {
+ Objects.requireNonNull(listener, "listener should not be null");
+
+ synchronized (mKeyboardBacklightListenerLock) {
+ if (mKeyboardBacklightListeners == null) {
+ return;
+ }
+ mKeyboardBacklightListeners.removeIf((delegate) -> delegate.mListener == listener);
+ if (mKeyboardBacklightListeners.isEmpty()) {
+ try {
+ mIm.unregisterKeyboardBacklightListener(mKeyboardBacklightListener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ mKeyboardBacklightListeners = null;
+ mKeyboardBacklightListener = null;
+ }
+ }
+ }
+
+ /**
* 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.
@@ -2373,6 +2447,27 @@ public final class InputManager {
void onTabletModeChanged(long whenNanos, boolean inTabletMode);
}
+ /**
+ * A callback used to be notified about keyboard backlight state changes for keyboard device.
+ * The {@link #onKeyboardBacklightChanged(int, KeyboardBacklightState, boolean)} method
+ * will be called once after the listener is successfully registered to provide the initial
+ * keyboard backlight state of the device.
+ * @see #registerKeyboardBacklightListener(Executor, KeyboardBacklightListener)
+ * @see #unregisterKeyboardBacklightListener(KeyboardBacklightListener)
+ * @hide
+ */
+ public interface KeyboardBacklightListener {
+ /**
+ * Called when the keyboard backlight brightness level changes.
+ * @param deviceId the keyboard for which the backlight brightness changed.
+ * @param state the new keyboard backlight state, never null.
+ * @param isTriggeredByKeyPress whether brightness change was triggered by the user
+ * pressing up/down key on the keyboard.
+ */
+ void onKeyboardBacklightChanged(
+ int deviceId, @NonNull KeyboardBacklightState state, boolean isTriggeredByKeyPress);
+ }
+
private final class TabletModeChangedListener extends ITabletModeChangedListener.Stub {
@Override
public void onTabletModeChanged(long whenNanos, boolean inTabletMode) {
@@ -2481,4 +2576,59 @@ public final class InputManager {
}
}
}
+
+ // Implementation of the android.hardware.input.KeyboardBacklightState interface used to report
+ // the keyboard backlight state via the KeyboardBacklightListener interfaces.
+ private static final class LocalKeyboardBacklightState extends KeyboardBacklightState {
+
+ private final int mBrightnessLevel;
+ private final int mMaxBrightnessLevel;
+
+ LocalKeyboardBacklightState(int brightnesslevel, int maxBrightnessLevel) {
+ mBrightnessLevel = brightnesslevel;
+ mMaxBrightnessLevel = maxBrightnessLevel;
+ }
+
+ @Override
+ public int getBrightnessLevel() {
+ return mBrightnessLevel;
+ }
+
+ @Override
+ public int getMaxBrightnessLevel() {
+ return mMaxBrightnessLevel;
+ }
+ }
+
+ private static final class KeyboardBacklightListenerDelegate {
+ final KeyboardBacklightListener mListener;
+ final Executor mExecutor;
+
+ KeyboardBacklightListenerDelegate(KeyboardBacklightListener listener, Executor executor) {
+ mListener = listener;
+ mExecutor = executor;
+ }
+
+ void notifyKeyboardBacklightChange(int deviceId, IKeyboardBacklightState state,
+ boolean isTriggeredByKeyPress) {
+ mExecutor.execute(() ->
+ mListener.onKeyboardBacklightChanged(deviceId,
+ new LocalKeyboardBacklightState(state.brightnessLevel,
+ state.maxBrightnessLevel), isTriggeredByKeyPress));
+ }
+ }
+
+ private class LocalKeyboardBacklightListener extends IKeyboardBacklightListener.Stub {
+
+ @Override
+ public void onBrightnessChanged(int deviceId, IKeyboardBacklightState state,
+ boolean isTriggeredByKeyPress) {
+ synchronized (mKeyboardBacklightListenerLock) {
+ if (mKeyboardBacklightListeners == null) return;
+ for (KeyboardBacklightListenerDelegate delegate : mKeyboardBacklightListeners) {
+ delegate.notifyKeyboardBacklightChange(deviceId, state, isTriggeredByKeyPress);
+ }
+ }
+ }
+ }
}
diff --git a/core/java/android/hardware/input/KeyboardBacklightState.java b/core/java/android/hardware/input/KeyboardBacklightState.java
new file mode 100644
index 000000000000..4beaf6d36d7f
--- /dev/null
+++ b/core/java/android/hardware/input/KeyboardBacklightState.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+/**
+ * The KeyboardBacklightState class is a representation of a keyboard backlight which is a
+ * single-colored backlight that illuminates all the keys on the keyboard.
+ *
+ * @hide
+ */
+public abstract class KeyboardBacklightState {
+
+ /**
+ * Get the backlight brightness level in range [0, {@link #getMaxBrightnessLevel()}].
+ *
+ * @return backlight brightness level
+ */
+ public abstract int getBrightnessLevel();
+
+ /**
+ * Get the max backlight brightness level.
+ *
+ * @return max backlight brightness level
+ */
+ public abstract int getMaxBrightnessLevel();
+}
+
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 99152347106f..92ee393247a2 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2095,6 +2095,15 @@ public class UserManager {
}
/**
+ * Returns whether multiple admins are enabled on the device
+ * @hide
+ */
+ public static boolean isMultipleAdminEnabled() {
+ return Resources.getSystem()
+ .getBoolean(com.android.internal.R.bool.config_enableMultipleAdmins);
+ }
+
+ /**
* Checks whether the device is running in a headless system user mode.
*
* <p>Headless system user mode means the {@link #isSystemUser() system user} runs system
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 718e212b61b7..2bdd360c58a6 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -15174,6 +15174,15 @@ public final class Settings {
"low_power_mode_suggestion_params";
/**
+ * Whether low power mode reminder is enabled. If this value is 0, the device will not
+ * receive low power notification.
+ *
+ * @hide
+ */
+ public static final String LOW_POWER_MODE_REMINDER_ENABLED =
+ "low_power_mode_reminder_enabled";
+
+ /**
* If not 0, the activity manager will aggressively finish activities and
* processes as soon as they are no longer needed. If 0, the normal
* extended lifetime is used.
diff --git a/core/java/android/service/voice/AbstractHotwordDetector.java b/core/java/android/service/voice/AbstractDetector.java
index c90ab6777515..db0ede5a9e70 100644
--- a/core/java/android/service/voice/AbstractHotwordDetector.java
+++ b/core/java/android/service/voice/AbstractDetector.java
@@ -41,9 +41,17 @@ import com.android.internal.app.IVoiceInteractionManagerService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
-/** Base implementation of {@link HotwordDetector}. */
-abstract class AbstractHotwordDetector implements HotwordDetector {
- private static final String TAG = AbstractHotwordDetector.class.getSimpleName();
+/** Base implementation of {@link HotwordDetector}.
+ *
+ * This class provides methods to manage the detector lifecycle for both
+ * {@link HotwordDetectionService} and {@link VisualQueryDetectionService}. We keep the name of the
+ * interface {@link HotwordDetector} since {@link VisualQueryDetectionService} can be logically
+ * treated as a visual activation hotword detection and also because of the existing public
+ * interface. To avoid confusion on the naming between the trusted hotword framework and the actual
+ * isolated {@link HotwordDetectionService}, the hotword from the names is removed.
+ */
+abstract class AbstractDetector implements HotwordDetector {
+ private static final String TAG = AbstractDetector.class.getSimpleName();
private static final boolean DEBUG = false;
protected final Object mLock = new Object();
@@ -51,14 +59,14 @@ abstract class AbstractHotwordDetector implements HotwordDetector {
private final IVoiceInteractionManagerService mManagerService;
private final Handler mHandler;
private final HotwordDetector.Callback mCallback;
- private Consumer<AbstractHotwordDetector> mOnDestroyListener;
+ private Consumer<AbstractDetector> mOnDestroyListener;
private final AtomicBoolean mIsDetectorActive;
/**
* A token which is used by voice interaction system service to identify different detectors.
*/
private final IBinder mToken = new Binder();
- AbstractHotwordDetector(
+ AbstractDetector(
IVoiceInteractionManagerService managerService,
HotwordDetector.Callback callback) {
mManagerService = managerService;
@@ -139,7 +147,7 @@ abstract class AbstractHotwordDetector implements HotwordDetector {
}
}
- void registerOnDestroyListener(Consumer<AbstractHotwordDetector> onDestroyListener) {
+ void registerOnDestroyListener(Consumer<AbstractDetector> onDestroyListener) {
synchronized (mLock) {
if (mOnDestroyListener != null) {
throw new IllegalStateException("only one destroy listener can be registered");
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 9008bf7d48ec..e8e8f1ae5947 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -81,7 +81,7 @@ import java.util.Set;
* mark and track it as such.
*/
@SystemApi
-public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
+public class AlwaysOnHotwordDetector extends AbstractDetector {
//---- States of Keyphrase availability. Return codes for onAvailabilityChanged() ----//
/**
* Indicates that this hotword detector is no longer valid for any recognition
diff --git a/core/java/android/service/voice/SoftwareHotwordDetector.java b/core/java/android/service/voice/SoftwareHotwordDetector.java
index f1b774591394..ffc662163e8e 100644
--- a/core/java/android/service/voice/SoftwareHotwordDetector.java
+++ b/core/java/android/service/voice/SoftwareHotwordDetector.java
@@ -46,7 +46,7 @@ import java.io.PrintWriter;
*
* @hide
**/
-class SoftwareHotwordDetector extends AbstractHotwordDetector {
+class SoftwareHotwordDetector extends AbstractDetector {
private static final String TAG = SoftwareHotwordDetector.class.getSimpleName();
private static final boolean DEBUG = false;
diff --git a/core/java/android/service/voice/VisualQueryDetectionService.java b/core/java/android/service/voice/VisualQueryDetectionService.java
new file mode 100644
index 000000000000..d8266f3f7a0f
--- /dev/null
+++ b/core/java/android/service/voice/VisualQueryDetectionService.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.voice;
+
+import android.annotation.DurationMillisLong;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.ContentCaptureOptions;
+import android.content.Intent;
+import android.hardware.soundtrigger.SoundTrigger;
+import android.media.AudioFormat;
+import android.os.IBinder;
+import android.os.IRemoteCallback;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+import android.os.SharedMemory;
+import android.speech.IRecognitionServiceManager;
+import android.util.Log;
+import android.view.contentcapture.IContentCaptureManager;
+
+import java.util.function.IntConsumer;
+
+/**
+ * Implemented by an application that wants to offer query detection with visual signals.
+ *
+ * This service leverages visual signals such as camera frames to detect and stream queries from the
+ * device microphone to the {@link VoiceInteractionService}, without the support of hotword. The
+ * system will bind an application's {@link VoiceInteractionService} first. When
+ * {@link VoiceInteractionService#createVisualQueryDetector(PersistableBundle, SharedMemory,
+ * Executor, VisualQueryDetector.Callback)} is called, the system will bind the application's
+ * {@link VisualQueryDetectionService}. When requested from {@link VoiceInteractionService}, the
+ * system calls into the {@link VisualQueryDetectionService#onStartDetection(Callback)} to enable
+ * detection. This method MUST be implemented to support visual query detection service.
+ *
+ * Note: Methods in this class may be called concurrently.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class VisualQueryDetectionService extends Service
+ implements SandboxedDetectionServiceBase {
+
+ private static final String TAG = VisualQueryDetectionService.class.getSimpleName();
+
+ private static final long UPDATE_TIMEOUT_MILLIS = 20000;
+
+ /**
+ * The {@link Intent} that must be declared as handled by the service.
+ * To be supported, the service must also require the
+ * {@link android.Manifest.permission#BIND_VISUAL_QUERY_DETECTION_SERVICE} permission
+ * so that other applications can not abuse it.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE =
+ "android.service.voice.VisualQueryDetectionService";
+
+
+ /** @hide */
+ public static final String KEY_INITIALIZATION_STATUS = "initialization_status";
+
+
+ private final ISandboxedDetectionService mInterface = new ISandboxedDetectionService.Stub() {
+
+ @Override
+ public void updateState(PersistableBundle options, SharedMemory sharedMemory,
+ IRemoteCallback callback) throws RemoteException {
+ Log.v(TAG, "#updateState" + (callback != null ? " with callback" : ""));
+ VisualQueryDetectionService.this.onUpdateStateInternal(
+ options,
+ sharedMemory,
+ callback);
+ }
+
+ @Override
+ public void ping(IRemoteCallback callback) throws RemoteException {
+ callback.sendResult(null);
+ }
+
+ @Override
+ public void detectFromDspSource(
+ SoundTrigger.KeyphraseRecognitionEvent event,
+ AudioFormat audioFormat,
+ long timeoutMillis,
+ IDspHotwordDetectionCallback callback) {
+ throw new UnsupportedOperationException("Not supported by VisualQueryDetectionService");
+ }
+
+ @Override
+ public void detectFromMicrophoneSource(
+ ParcelFileDescriptor audioStream,
+ @HotwordDetectionService.AudioSource int audioSource,
+ AudioFormat audioFormat,
+ PersistableBundle options,
+ IDspHotwordDetectionCallback callback) {
+ throw new UnsupportedOperationException("Not supported by VisualQueryDetectionService");
+ }
+
+ @Override
+ public void updateAudioFlinger(IBinder audioFlinger) {
+ Log.v(TAG, "Ignore #updateAudioFlinger");
+ }
+
+ @Override
+ public void updateContentCaptureManager(IContentCaptureManager manager,
+ ContentCaptureOptions options) {
+ Log.v(TAG, "Ignore #updateContentCaptureManager");
+ }
+
+ @Override
+ public void updateRecognitionServiceManager(IRecognitionServiceManager manager) {
+ Log.v(TAG, "Ignore #updateRecognitionServiceManager");
+ }
+
+ @Override
+ public void stopDetection() {
+ throw new UnsupportedOperationException("Not supported by VisualQueryDetectionService");
+ }
+ };
+
+ /**
+ * {@inheritDoc}
+ * @hide
+ */
+ @Override
+ @SystemApi
+ public void onUpdateState(
+ @Nullable PersistableBundle options,
+ @Nullable SharedMemory sharedMemory,
+ @DurationMillisLong long callbackTimeoutMillis,
+ @Nullable IntConsumer statusCallback) {
+ }
+
+ @Override
+ @Nullable
+ public IBinder onBind(@NonNull Intent intent) {
+ if (SERVICE_INTERFACE.equals(intent.getAction())) {
+ return mInterface.asBinder();
+ }
+ Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": "
+ + intent);
+ return null;
+ }
+
+ private void onUpdateStateInternal(@Nullable PersistableBundle options,
+ @Nullable SharedMemory sharedMemory, IRemoteCallback callback) {
+ IntConsumer intConsumer =
+ SandboxedDetectionServiceBase.createInitializationStatusConsumer(callback);
+ onUpdateState(options, sharedMemory, UPDATE_TIMEOUT_MILLIS, intConsumer);
+ }
+
+ /**
+ * This is called after the service is set up and the client should open the camera and the
+ * microphone to start recognition.
+ *
+ * Called when the {@link VoiceInteractionService} requests that this service
+ * {@link HotwordDetector#startRecognition()} start recognition on audio coming directly
+ * from the device microphone.
+ *
+ * @param callback The callback to use for responding to the detection request.
+ *
+ */
+ public void onStartDetection(@NonNull Callback callback) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Called when the {@link VoiceInteractionService}
+ * {@link HotwordDetector#stopRecognition()} requests that recognition be stopped.
+ */
+ public void onStopDetection() {
+ }
+
+ /**
+ * Callback for sending out signals and returning query results.
+ */
+ public static final class Callback {
+ //TODO: Add callback to send signals to VIS and SysUI.
+ }
+
+}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index a59578ee8d9d..9e1518d899e0 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -168,7 +168,7 @@ public class VoiceInteractionService extends Service {
private KeyphraseEnrollmentInfo mKeyphraseEnrollmentInfo;
- private final Set<HotwordDetector> mActiveHotwordDetectors = new ArraySet<>();
+ private final Set<HotwordDetector> mActiveDetectors = new ArraySet<>();
/**
* Called when a user has activated an affordance to launch voice assist from the Keyguard.
@@ -319,7 +319,7 @@ public class VoiceInteractionService extends Service {
private void onSoundModelsChangedInternal() {
synchronized (this) {
// TODO: Stop recognition if a sound model that was being recognized gets deleted.
- mActiveHotwordDetectors.forEach(detector -> {
+ mActiveDetectors.forEach(detector -> {
if (detector instanceof AlwaysOnHotwordDetector) {
((AlwaysOnHotwordDetector) detector).onSoundModelsChanged();
}
@@ -429,7 +429,7 @@ public class VoiceInteractionService extends Service {
// Allow only one concurrent recognition via the APIs.
safelyShutdownAllHotwordDetectors();
} else {
- for (HotwordDetector detector : mActiveHotwordDetectors) {
+ for (HotwordDetector detector : mActiveDetectors) {
if (detector.isUsingSandboxedDetectionService()
!= supportHotwordDetectionService) {
throw new IllegalStateException(
@@ -447,13 +447,13 @@ public class VoiceInteractionService extends Service {
callback, mKeyphraseEnrollmentInfo, mSystemService,
getApplicationContext().getApplicationInfo().targetSdkVersion,
supportHotwordDetectionService);
- mActiveHotwordDetectors.add(dspDetector);
+ mActiveDetectors.add(dspDetector);
try {
dspDetector.registerOnDestroyListener(this::onHotwordDetectorDestroyed);
dspDetector.initialize(options, sharedMemory);
} catch (Exception e) {
- mActiveHotwordDetectors.remove(dspDetector);
+ mActiveDetectors.remove(dspDetector);
dspDetector.destroy();
throw e;
}
@@ -512,7 +512,7 @@ public class VoiceInteractionService extends Service {
// Allow only one concurrent recognition via the APIs.
safelyShutdownAllHotwordDetectors();
} else {
- for (HotwordDetector detector : mActiveHotwordDetectors) {
+ for (HotwordDetector detector : mActiveDetectors) {
if (!detector.isUsingSandboxedDetectionService()) {
throw new IllegalStateException(
"It disallows to create trusted and non-trusted detectors "
@@ -528,14 +528,14 @@ public class VoiceInteractionService extends Service {
SoftwareHotwordDetector softwareHotwordDetector =
new SoftwareHotwordDetector(
mSystemService, null, callback);
- mActiveHotwordDetectors.add(softwareHotwordDetector);
+ mActiveDetectors.add(softwareHotwordDetector);
try {
softwareHotwordDetector.registerOnDestroyListener(
this::onHotwordDetectorDestroyed);
softwareHotwordDetector.initialize(options, sharedMemory);
} catch (Exception e) {
- mActiveHotwordDetectors.remove(softwareHotwordDetector);
+ mActiveDetectors.remove(softwareHotwordDetector);
softwareHotwordDetector.destroy();
throw e;
}
@@ -586,7 +586,7 @@ public class VoiceInteractionService extends Service {
private void safelyShutdownAllHotwordDetectors() {
synchronized (mLock) {
- mActiveHotwordDetectors.forEach(detector -> {
+ mActiveDetectors.forEach(detector -> {
try {
detector.destroy();
} catch (Exception ex) {
@@ -598,13 +598,13 @@ public class VoiceInteractionService extends Service {
private void onHotwordDetectorDestroyed(@NonNull HotwordDetector detector) {
synchronized (mLock) {
- mActiveHotwordDetectors.remove(detector);
+ mActiveDetectors.remove(detector);
shutdownHotwordDetectionServiceIfRequiredLocked();
}
}
private void shutdownHotwordDetectionServiceIfRequiredLocked() {
- for (HotwordDetector detector : mActiveHotwordDetectors) {
+ for (HotwordDetector detector : mActiveDetectors) {
if (detector.isUsingSandboxedDetectionService()) {
return;
}
@@ -638,11 +638,11 @@ public class VoiceInteractionService extends Service {
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("VOICE INTERACTION");
synchronized (mLock) {
- pw.println(" HotwordDetector(s)");
- if (mActiveHotwordDetectors.size() == 0) {
+ pw.println(" Sandboxed Detector(s)");
+ if (mActiveDetectors.size() == 0) {
pw.println(" NULL");
} else {
- mActiveHotwordDetectors.forEach(detector -> {
+ mActiveDetectors.forEach(detector -> {
detector.dump(" ", pw);
pw.println();
});
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 0eab81c03259..44afb89e1ccc 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -252,7 +252,7 @@ public abstract class WallpaperService extends Service {
final Rect mDispatchedStableInsets = new Rect();
DisplayCutout mDispatchedDisplayCutout = DisplayCutout.NO_CUTOUT;
final InsetsState mInsetsState = new InsetsState();
- final InsetsSourceControl[] mTempControls = new InsetsSourceControl[0];
+ final InsetsSourceControl.Array mTempControls = new InsetsSourceControl.Array();
final MergedConfiguration mMergedConfiguration = new MergedConfiguration();
final Bundle mSyncSeqIdBundle = new Bundle();
private final Point mSurfaceSize = new Point();
diff --git a/core/java/android/view/EventLogTags.logtags b/core/java/android/view/EventLogTags.logtags
index 1ad3472e4c75..cc0b18a652d5 100644
--- a/core/java/android/view/EventLogTags.logtags
+++ b/core/java/android/view/EventLogTags.logtags
@@ -44,6 +44,12 @@ option java_package android.view
32007 imf_ime_anim_finish (token|3),(animation type|1),(alpha|5),(shown|1),(insets|3)
# IME animation is canceled.
32008 imf_ime_anim_cancel (token|3),(animation type|1),(pending insets|3)
+# IME remote animation is started.
+32009 imf_ime_remote_anim_start (token|3),(displayId|1),(direction|1),(alpha|5),(startY|5),(endY|5),(leash|3),(insets|3),(surface position|3),(ime frame|3)
+# IME remote animation is end.
+32010 imf_ime_remote_anim_end (token|3),(displayId|1),(direction|1),(endY|5),(leash|3),(insets|3),(surface position|3),(ime frame|3)
+# IME remote animation is canceled.
+32011 imf_ime_remote_anim_cancel (token|3),(displayId|1),(insets|3)
# 62000 - 62199 reserved for inputflinger
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 0aba80db5378..e38e5370ec45 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -721,8 +721,7 @@ interface IWindowManager
*
* @return {@code true} if system bars are always consumed.
*/
- boolean getWindowInsets(in WindowManager.LayoutParams attrs, int displayId,
- out InsetsState outInsetsState);
+ boolean getWindowInsets(int displayId, in IBinder token, out InsetsState outInsetsState);
/**
* Returns a list of {@link android.view.DisplayInfo} for the logical display. This is not
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index e4d878a59c11..943d64ab986c 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -49,12 +49,12 @@ interface IWindowSession {
int addToDisplay(IWindow window, in WindowManager.LayoutParams attrs,
in int viewVisibility, in int layerStackId, int requestedVisibleTypes,
out InputChannel outInputChannel, out InsetsState insetsState,
- out InsetsSourceControl[] activeControls, out Rect attachedFrame,
+ out InsetsSourceControl.Array activeControls, out Rect attachedFrame,
out float[] sizeCompatScale);
int addToDisplayAsUser(IWindow window, in WindowManager.LayoutParams attrs,
in int viewVisibility, in int layerStackId, in int userId, int requestedVisibleTypes,
out InputChannel outInputChannel, out InsetsState insetsState,
- out InsetsSourceControl[] activeControls, out Rect attachedFrame,
+ out InsetsSourceControl.Array activeControls, out Rect attachedFrame,
out float[] sizeCompatScale);
int addToDisplayWithoutInputChannel(IWindow window, in WindowManager.LayoutParams attrs,
in int viewVisibility, in int layerStackId, out InsetsState insetsState,
@@ -91,7 +91,7 @@ interface IWindowSession {
int requestedWidth, int requestedHeight, int viewVisibility,
int flags, int seq, int lastSyncSeqId, out ClientWindowFrames outFrames,
out MergedConfiguration outMergedConfiguration, out SurfaceControl outSurfaceControl,
- out InsetsState insetsState, out InsetsSourceControl[] activeControls,
+ out InsetsState insetsState, out InsetsSourceControl.Array activeControls,
out Bundle bundle);
/**
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 89c7a360a91c..5a9a2520d839 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -26,7 +26,6 @@ import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.hardware.BatteryState;
import android.hardware.SensorManager;
-import android.hardware.input.InputDeviceCountryCode;
import android.hardware.input.InputDeviceIdentifier;
import android.hardware.input.InputManager;
import android.hardware.lights.LightsManager;
@@ -75,8 +74,6 @@ public final class InputDevice implements Parcelable {
private final int mSources;
private final int mKeyboardType;
private final KeyCharacterMap mKeyCharacterMap;
- @InputDeviceCountryCode
- private final int mCountryCode;
@Nullable
private final String mKeyboardLanguageTag;
@Nullable
@@ -468,10 +465,9 @@ public final class InputDevice implements Parcelable {
*/
private InputDevice(int id, int generation, int controllerNumber, String name, int vendorId,
int productId, String descriptor, boolean isExternal, int sources, int keyboardType,
- KeyCharacterMap keyCharacterMap, @InputDeviceCountryCode int countryCode,
- @Nullable String keyboardLanguageTag, @Nullable String keyboardLayoutType,
- boolean hasVibrator, boolean hasMicrophone, boolean hasButtonUnderPad,
- boolean hasSensor, boolean hasBattery, boolean supportsUsi) {
+ KeyCharacterMap keyCharacterMap, @Nullable String keyboardLanguageTag,
+ @Nullable String keyboardLayoutType, boolean hasVibrator, boolean hasMicrophone,
+ boolean hasButtonUnderPad, boolean hasSensor, boolean hasBattery, boolean supportsUsi) {
mId = id;
mGeneration = generation;
mControllerNumber = controllerNumber;
@@ -483,7 +479,6 @@ public final class InputDevice implements Parcelable {
mSources = sources;
mKeyboardType = keyboardType;
mKeyCharacterMap = keyCharacterMap;
- mCountryCode = countryCode;
if (keyboardLanguageTag != null) {
mKeyboardLanguageTag = ULocale
.createCanonical(ULocale.forLanguageTag(keyboardLanguageTag))
@@ -513,7 +508,6 @@ public final class InputDevice implements Parcelable {
mIsExternal = in.readInt() != 0;
mSources = in.readInt();
mKeyboardType = in.readInt();
- mCountryCode = in.readInt();
mKeyboardLanguageTag = in.readString8();
mKeyboardLayoutType = in.readString8();
mHasVibrator = in.readInt() != 0;
@@ -558,8 +552,6 @@ public final class InputDevice implements Parcelable {
private boolean mHasButtonUnderPad = false;
private boolean mHasSensor = false;
private boolean mHasBattery = false;
- @InputDeviceCountryCode
- private int mCountryCode = InputDeviceCountryCode.INVALID;
private String mKeyboardLanguageTag = null;
private String mKeyboardLayoutType = null;
private boolean mSupportsUsi = false;
@@ -660,12 +652,6 @@ public final class InputDevice implements Parcelable {
return this;
}
- /** @see InputDevice#getCountryCode() */
- public Builder setCountryCode(@InputDeviceCountryCode int countryCode) {
- mCountryCode = countryCode;
- return this;
- }
-
/** @see InputDevice#getKeyboardLanguageTag() */
public Builder setKeyboardLanguageTag(String keyboardLanguageTag) {
mKeyboardLanguageTag = keyboardLanguageTag;
@@ -688,8 +674,8 @@ public final class InputDevice implements Parcelable {
public InputDevice build() {
return new InputDevice(mId, mGeneration, mControllerNumber, mName, mVendorId,
mProductId, mDescriptor, mIsExternal, mSources, mKeyboardType, mKeyCharacterMap,
- mCountryCode, mKeyboardLanguageTag, mKeyboardLayoutType, mHasVibrator,
- mHasMicrophone, mHasButtonUnderPad, mHasSensor, mHasBattery, mSupportsUsi);
+ mKeyboardLanguageTag, mKeyboardLayoutType, mHasVibrator, mHasMicrophone,
+ mHasButtonUnderPad, mHasSensor, mHasBattery, mSupportsUsi);
}
}
@@ -909,16 +895,6 @@ public final class InputDevice implements Parcelable {
}
/**
- * Gets Country code associated with the device
- *
- * @hide
- */
- @InputDeviceCountryCode
- public int getCountryCode() {
- return mCountryCode;
- }
-
- /**
* Returns the keyboard language as an IETF
* <a href="https://tools.ietf.org/html/bcp47">BCP-47</a>
* conformant tag if available.
@@ -1393,7 +1369,6 @@ public final class InputDevice implements Parcelable {
out.writeInt(mIsExternal ? 1 : 0);
out.writeInt(mSources);
out.writeInt(mKeyboardType);
- out.writeInt(mCountryCode);
out.writeString8(mKeyboardLanguageTag);
out.writeString8(mKeyboardLayoutType);
out.writeInt(mHasVibrator ? 1 : 0);
@@ -1445,8 +1420,6 @@ public final class InputDevice implements Parcelable {
}
description.append("\n");
- description.append(" Country Code: ").append(mCountryCode).append("\n");
-
description.append(" Has Vibrator: ").append(mHasVibrator).append("\n");
description.append(" Has Sensor: ").append(mHasSensor).append("\n");
@@ -1457,6 +1430,15 @@ public final class InputDevice implements Parcelable {
description.append(" Supports USI: ").append(mSupportsUsi).append("\n");
+ if (mKeyboardLanguageTag != null) {
+ description.append(" Keyboard language tag: ").append(mKeyboardLanguageTag).append(
+ "\n");
+ }
+
+ if (mKeyboardLayoutType != null) {
+ description.append(" Keyboard layout type: ").append(mKeyboardLayoutType).append("\n");
+ }
+
description.append(" Sources: 0x").append(Integer.toHexString(mSources)).append(" (");
appendSourceDescriptionIfApplicable(description, SOURCE_KEYBOARD, "keyboard");
appendSourceDescriptionIfApplicable(description, SOURCE_DPAD, "dpad");
diff --git a/core/java/android/view/InsetsSourceControl.aidl b/core/java/android/view/InsetsSourceControl.aidl
index 755bf456658d..7301ee761522 100644
--- a/core/java/android/view/InsetsSourceControl.aidl
+++ b/core/java/android/view/InsetsSourceControl.aidl
@@ -17,3 +17,4 @@
package android.view;
parcelable InsetsSourceControl;
+parcelable InsetsSourceControl.Array;
diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java
index 610cfe40ebce..c849cb5bfe2b 100644
--- a/core/java/android/view/InsetsSourceControl.java
+++ b/core/java/android/view/InsetsSourceControl.java
@@ -257,4 +257,52 @@ public class InsetsSourceControl implements Parcelable {
}
proto.end(token);
}
+
+ /**
+ * Used to obtain the array from the argument of a binder call. In this way, the length of the
+ * array can be dynamic.
+ */
+ public static class Array implements Parcelable {
+
+ private @Nullable InsetsSourceControl[] mControls;
+
+ public Array() {
+ }
+
+ public Array(Parcel in) {
+ readFromParcel(in);
+ }
+
+ public void set(@Nullable InsetsSourceControl[] controls) {
+ mControls = controls;
+ }
+
+ public @Nullable InsetsSourceControl[] get() {
+ return mControls;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public void readFromParcel(Parcel in) {
+ mControls = in.createTypedArray(InsetsSourceControl.CREATOR);
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeTypedArray(mControls, flags);
+ }
+
+ public static final @NonNull Creator<Array> CREATOR = new Creator<>() {
+ public Array createFromParcel(Parcel in) {
+ return new Array(in);
+ }
+
+ public Array[] newArray(int size) {
+ return new Array[size];
+ }
+ };
+ }
}
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 4fbb249c507f..1ff7ae662da0 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -1491,11 +1491,23 @@ public final class MotionEvent extends InputEvent implements Parcelable {
*/
public static final int CLASSIFICATION_TWO_FINGER_SWIPE = 3;
+ /**
+ * Classification constant: multi-finger swipe.
+ *
+ * The current event stream represents the user swiping with three or more fingers on a
+ * touchpad. Unlike two-finger swipes, these are only to be handled by the system UI, which is
+ * why they have a separate constant from two-finger swipes.
+ *
+ * @see #getClassification
+ * @hide
+ */
+ public static final int CLASSIFICATION_MULTI_FINGER_SWIPE = 4;
+
/** @hide */
@Retention(SOURCE)
@IntDef(prefix = { "CLASSIFICATION" }, value = {
CLASSIFICATION_NONE, CLASSIFICATION_AMBIGUOUS_GESTURE, CLASSIFICATION_DEEP_PRESS,
- CLASSIFICATION_TWO_FINGER_SWIPE})
+ CLASSIFICATION_TWO_FINGER_SWIPE, CLASSIFICATION_MULTI_FINGER_SWIPE})
public @interface Classification {};
/**
@@ -3941,7 +3953,8 @@ public final class MotionEvent extends InputEvent implements Parcelable {
return "DEEP_PRESS";
case CLASSIFICATION_TWO_FINGER_SWIPE:
return "TWO_FINGER_SWIPE";
-
+ case CLASSIFICATION_MULTI_FINGER_SWIPE:
+ return "MULTI_FINGER_SWIPE";
}
return "UNKNOWN";
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index c0f4731aeaf4..c4da009efaf3 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -23,7 +23,6 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.InputDevice.SOURCE_CLASS_NONE;
import static android.view.InsetsState.ITYPE_IME;
-import static android.view.InsetsState.SIZE;
import static android.view.View.PFLAG_DRAW_ANIMATION;
import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
@@ -705,7 +704,7 @@ public final class ViewRootImpl implements ViewParent,
private int mRelayoutSeq;
private final Rect mWinFrameInScreen = new Rect();
private final InsetsState mTempInsets = new InsetsState();
- private final InsetsSourceControl[] mTempControls = new InsetsSourceControl[SIZE];
+ private final InsetsSourceControl.Array mTempControls = new InsetsSourceControl.Array();
private final WindowConfiguration mTempWinConfig = new WindowConfiguration();
private float mInvCompatScale = 1f;
final ViewTreeObserver.InternalInsetsInfo mLastGivenInsets
@@ -1264,7 +1263,7 @@ public final class ViewRootImpl implements ViewParent,
}
if (mTranslator != null) {
mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
- mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);
+ mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls.get());
mTranslator.translateRectInScreenToAppWindow(attachedFrame);
}
mTmpFrames.attachedFrame = attachedFrame;
@@ -1288,7 +1287,7 @@ public final class ViewRootImpl implements ViewParent,
(res & WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS) != 0;
mPendingAlwaysConsumeSystemBars = mAttachInfo.mAlwaysConsumeSystemBars;
mInsetsController.onStateChanged(mTempInsets);
- mInsetsController.onControlsChanged(mTempControls);
+ mInsetsController.onControlsChanged(mTempControls.get());
final InsetsState state = mInsetsController.getState();
final Rect displayCutoutSafe = mTempRect;
state.getDisplayCutoutSafe(displayCutoutSafe);
@@ -8334,12 +8333,12 @@ public final class ViewRootImpl implements ViewParent,
mTranslator.translateRectInScreenToAppWindow(mTmpFrames.displayFrame);
mTranslator.translateRectInScreenToAppWindow(mTmpFrames.attachedFrame);
mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
- mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);
+ mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls.get());
}
mInvCompatScale = 1f / mTmpFrames.compatScale;
CompatibilityInfo.applyOverrideScaleIfNeeded(mPendingMergedConfiguration);
mInsetsController.onStateChanged(mTempInsets);
- mInsetsController.onControlsChanged(mTempControls);
+ mInsetsController.onControlsChanged(mTempControls.get());
mPendingAlwaysConsumeSystemBars =
(relayoutResult & RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS) != 0;
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index f375ccb7e207..43cf75859ce9 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -814,6 +814,45 @@ public interface WindowManager extends ViewManager {
}
/**
+ * Activity level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that the activity can be opted-in or opted-out
+ * from the compatibility treatment that avoids {@link
+ * android.app.Activity#setRequestedOrientation} loops. The loop can be trigerred by
+ * ignoreRequestedOrientation display setting enabled on the device or by the landscape natural
+ * orientation of the device.
+ *
+ * <p>The treatment is disabled by default but device manufacturers can enable the treatment
+ * using their discretion to improve display compatibility.
+ *
+ * <p>With this property set to {@code true}, the system could ignore {@link
+ * android.app.Activity#setRequestedOrientation} call from an app if one of the following
+ * conditions are true:
+ * <ul>
+ * <li>Activity is relaunching due to the previous {@link
+ * android.app.Activity#setRequestedOrientation} call.
+ * <li>Camera compatibility force rotation treatment is active for the package.
+ * </ul>
+ *
+ * <p>Setting this property to {@code false} informs the system that the activity must be
+ * opted-out from the compatibility treatment even if the device manufacturer has opted the app
+ * into the treatment.
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * &lt;activity&gt;
+ * &lt;property
+ * android:name="android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION"
+ * android:value="true|false"/&gt;
+ * &lt;/activity&gt;
+ * </pre>
+ *
+ * @hide
+ */
+ // TODO(b/263984287): Make this public API.
+ String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION =
+ "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
+
+ /**
* @hide
*/
public static final String PARCEL_KEY_SHORTCUTS_ARRAY = "shortcuts_array";
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 4c95728548c5..edf33f15a9ca 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -148,7 +148,7 @@ public class WindowlessWindowManager implements IWindowSession {
public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, @InsetsType int requestedVisibleTypes,
InputChannel outInputChannel, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls, Rect outAttachedFrame,
+ InsetsSourceControl.Array outActiveControls, Rect outAttachedFrame,
float[] outSizeCompatScale) {
final SurfaceControl.Builder b = new SurfaceControl.Builder(mSurfaceSession)
.setFormat(attrs.format)
@@ -200,7 +200,7 @@ public class WindowlessWindowManager implements IWindowSession {
public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, int userId, @InsetsType int requestedVisibleTypes,
InputChannel outInputChannel, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls, Rect outAttachedFrame,
+ InsetsSourceControl.Array outActiveControls, Rect outAttachedFrame,
float[] outSizeCompatScale) {
return addToDisplay(window, attrs, viewVisibility, displayId, requestedVisibleTypes,
outInputChannel, outInsetsState, outActiveControls, outAttachedFrame,
@@ -290,7 +290,7 @@ public class WindowlessWindowManager implements IWindowSession {
int requestedWidth, int requestedHeight, int viewFlags, int flags, int seq,
int lastSyncSeqId, ClientWindowFrames outFrames,
MergedConfiguration outMergedConfiguration, SurfaceControl outSurfaceControl,
- InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,
+ InsetsState outInsetsState, InsetsSourceControl.Array outActiveControls,
Bundle outSyncSeqIdBundle) {
final State state;
synchronized (this) {
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index 9ed5c29089b2..3b6ec800836a 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -44,8 +44,7 @@ public interface ImeTracker {
String TAG = "ImeTracker";
/** The debug flag for IME visibility event log. */
- // TODO(b/239501597) : Have a system property to control this flag.
- boolean DEBUG_IME_VISIBILITY = false;
+ boolean DEBUG_IME_VISIBILITY = SystemProperties.getBoolean("persist.debug.imf_event", false);
/** The message to indicate if there is no valid {@link Token}. */
String TOKEN_NONE = "TOKEN_NONE";
diff --git a/core/java/android/window/TaskFragmentAnimationParams.aidl b/core/java/android/window/TaskFragmentAnimationParams.aidl
new file mode 100644
index 000000000000..04dee58089d4
--- /dev/null
+++ b/core/java/android/window/TaskFragmentAnimationParams.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+/**
+ * Data object for animation related override of TaskFragment.
+ * @hide
+ */
+parcelable TaskFragmentAnimationParams;
diff --git a/core/java/android/window/TaskFragmentAnimationParams.java b/core/java/android/window/TaskFragmentAnimationParams.java
new file mode 100644
index 000000000000..a600a4db42b8
--- /dev/null
+++ b/core/java/android/window/TaskFragmentAnimationParams.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import android.annotation.ColorInt;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Data object for animation related override of TaskFragment.
+ * @hide
+ */
+// TODO(b/206557124): Add more animation customization options.
+public final class TaskFragmentAnimationParams implements Parcelable {
+
+ /** The default {@link TaskFragmentAnimationParams} to use when there is no app override. */
+ public static final TaskFragmentAnimationParams DEFAULT =
+ new TaskFragmentAnimationParams.Builder().build();
+
+ @ColorInt
+ private final int mAnimationBackgroundColor;
+
+ private TaskFragmentAnimationParams(@ColorInt int animationBackgroundColor) {
+ mAnimationBackgroundColor = animationBackgroundColor;
+ }
+
+ /**
+ * The {@link ColorInt} to use for the background during the animation with this TaskFragment if
+ * the animation requires a background.
+ *
+ * The default value is {@code 0}, which is to use the theme window background.
+ */
+ @ColorInt
+ public int getAnimationBackgroundColor() {
+ return mAnimationBackgroundColor;
+ }
+
+ private TaskFragmentAnimationParams(Parcel in) {
+ mAnimationBackgroundColor = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mAnimationBackgroundColor);
+ }
+
+ @NonNull
+ public static final Creator<TaskFragmentAnimationParams> CREATOR =
+ new Creator<TaskFragmentAnimationParams>() {
+ @Override
+ public TaskFragmentAnimationParams createFromParcel(Parcel in) {
+ return new TaskFragmentAnimationParams(in);
+ }
+
+ @Override
+ public TaskFragmentAnimationParams[] newArray(int size) {
+ return new TaskFragmentAnimationParams[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return "TaskFragmentAnimationParams{"
+ + " animationBgColor=" + Integer.toHexString(mAnimationBackgroundColor)
+ + "}";
+ }
+
+ @Override
+ public int hashCode() {
+ return mAnimationBackgroundColor;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof TaskFragmentAnimationParams)) {
+ return false;
+ }
+ final TaskFragmentAnimationParams other = (TaskFragmentAnimationParams) obj;
+ return mAnimationBackgroundColor == other.mAnimationBackgroundColor;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Builder to construct the {@link TaskFragmentAnimationParams}. */
+ public static final class Builder {
+
+ @ColorInt
+ private int mAnimationBackgroundColor = 0;
+
+ /**
+ * Sets the {@link ColorInt} to use for the background during the animation with this
+ * TaskFragment if the animation requires a background. The default value is
+ * {@code 0}, which is to use the theme window background.
+ *
+ * @param color a packed color int, {@code AARRGGBB}, for the animation background color.
+ * @return this {@link Builder}.
+ */
+ @NonNull
+ public Builder setAnimationBackgroundColor(@ColorInt int color) {
+ mAnimationBackgroundColor = color;
+ return this;
+ }
+
+ /** Constructs the {@link TaskFragmentAnimationParams}. */
+ @NonNull
+ public TaskFragmentAnimationParams build() {
+ return new TaskFragmentAnimationParams(mAnimationBackgroundColor);
+ }
+ }
+}
diff --git a/core/java/android/window/TaskFragmentOperation.aidl b/core/java/android/window/TaskFragmentOperation.aidl
new file mode 100644
index 000000000000..c21700c6634b
--- /dev/null
+++ b/core/java/android/window/TaskFragmentOperation.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+/**
+ * Data object of params for TaskFragment related {@link WindowContainerTransaction} operation.
+ * @hide
+ */
+parcelable TaskFragmentOperation;
diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java
new file mode 100644
index 000000000000..bec6c58e4c8a
--- /dev/null
+++ b/core/java/android/window/TaskFragmentOperation.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Data object of params for TaskFragment related {@link WindowContainerTransaction} operation.
+ *
+ * @see WindowContainerTransaction#setTaskFragmentOperation(IBinder, TaskFragmentOperation).
+ * @hide
+ */
+// TODO(b/263436063): move other TaskFragment related operation here.
+public final class TaskFragmentOperation implements Parcelable {
+
+ /** Sets the {@link TaskFragmentAnimationParams} for the given TaskFragment. */
+ public static final int OP_TYPE_SET_ANIMATION_PARAMS = 0;
+
+ @IntDef(prefix = { "OP_TYPE_" }, value = {
+ OP_TYPE_SET_ANIMATION_PARAMS
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface OperationType {}
+
+ @OperationType
+ private final int mOpType;
+
+ @Nullable
+ private final TaskFragmentAnimationParams mAnimationParams;
+
+ private TaskFragmentOperation(@OperationType int opType,
+ @Nullable TaskFragmentAnimationParams animationParams) {
+ mOpType = opType;
+ mAnimationParams = animationParams;
+ }
+
+ private TaskFragmentOperation(Parcel in) {
+ mOpType = in.readInt();
+ mAnimationParams = in.readTypedObject(TaskFragmentAnimationParams.CREATOR);
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mOpType);
+ dest.writeTypedObject(mAnimationParams, flags);
+ }
+
+ @NonNull
+ public static final Creator<TaskFragmentOperation> CREATOR =
+ new Creator<TaskFragmentOperation>() {
+ @Override
+ public TaskFragmentOperation createFromParcel(Parcel in) {
+ return new TaskFragmentOperation(in);
+ }
+
+ @Override
+ public TaskFragmentOperation[] newArray(int size) {
+ return new TaskFragmentOperation[size];
+ }
+ };
+
+ /**
+ * Gets the {@link OperationType} of this {@link TaskFragmentOperation}.
+ */
+ @OperationType
+ public int getOpType() {
+ return mOpType;
+ }
+
+ /**
+ * Gets the animation related override of TaskFragment.
+ */
+ @Nullable
+ public TaskFragmentAnimationParams getAnimationParams() {
+ return mAnimationParams;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("TaskFragmentOperation{ opType=").append(mOpType);
+ if (mAnimationParams != null) {
+ sb.append(", animationParams=").append(mAnimationParams);
+ }
+
+ sb.append('}');
+ return sb.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mOpType;
+ result = result * 31 + mAnimationParams.hashCode();
+ return result;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof TaskFragmentOperation)) {
+ return false;
+ }
+ final TaskFragmentOperation other = (TaskFragmentOperation) obj;
+ return mOpType == other.mOpType
+ && Objects.equals(mAnimationParams, other.mAnimationParams);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Builder to construct the {@link TaskFragmentOperation}. */
+ public static final class Builder {
+
+ @OperationType
+ private final int mOpType;
+
+ @Nullable
+ private TaskFragmentAnimationParams mAnimationParams;
+
+ /**
+ * @param opType the {@link OperationType} of this {@link TaskFragmentOperation}.
+ */
+ public Builder(@OperationType int opType) {
+ mOpType = opType;
+ }
+
+ /**
+ * Sets the {@link TaskFragmentAnimationParams} for the given TaskFragment.
+ */
+ @NonNull
+ public Builder setAnimationParams(@Nullable TaskFragmentAnimationParams animationParams) {
+ mAnimationParams = animationParams;
+ return this;
+ }
+
+ /**
+ * Constructs the {@link TaskFragmentOperation}.
+ */
+ @NonNull
+ public TaskFragmentOperation build() {
+ return new TaskFragmentOperation(mOpType, mAnimationParams);
+ }
+ }
+}
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 5793674caaa6..647ccf51b5ef 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -751,6 +751,30 @@ public final class WindowContainerTransaction implements Parcelable {
}
/**
+ * Sets the {@link TaskFragmentOperation} to apply to the given TaskFragment.
+ *
+ * @param fragmentToken client assigned unique token to create TaskFragment with specified in
+ * {@link TaskFragmentCreationParams#getFragmentToken()}.
+ * @param taskFragmentOperation the {@link TaskFragmentOperation} to apply to the given
+ * TaskFramgent.
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction setTaskFragmentOperation(@NonNull IBinder fragmentToken,
+ @NonNull TaskFragmentOperation taskFragmentOperation) {
+ Objects.requireNonNull(fragmentToken);
+ Objects.requireNonNull(taskFragmentOperation);
+ final HierarchyOp hierarchyOp =
+ new HierarchyOp.Builder(
+ HierarchyOp.HIERARCHY_OP_TYPE_SET_TASK_FRAGMENT_OPERATION)
+ .setContainer(fragmentToken)
+ .setTaskFragmentOperation(taskFragmentOperation)
+ .build();
+ mHierarchyOps.add(hierarchyOp);
+ return this;
+ }
+
+ /**
* Sets/removes the always on top flag for this {@code windowContainer}. See
* {@link com.android.server.wm.ConfigurationContainer#setAlwaysOnTop(boolean)}.
* Please note that this method is only intended to be used for a
@@ -1261,6 +1285,7 @@ public final class WindowContainerTransaction implements Parcelable {
public static final int HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT = 22;
public static final int HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS = 23;
public static final int HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH = 24;
+ public static final int HIERARCHY_OP_TYPE_SET_TASK_FRAGMENT_OPERATION = 25;
// The following key(s) are for use with mLaunchOptions:
// When launching a task (eg. from recents), this is the taskId to be launched.
@@ -1301,10 +1326,14 @@ public final class WindowContainerTransaction implements Parcelable {
@Nullable
private Intent mActivityIntent;
- // Used as options for WindowContainerTransaction#createTaskFragment().
+ /** Used as options for {@link #createTaskFragment}. */
@Nullable
private TaskFragmentCreationParams mTaskFragmentCreationOptions;
+ /** Used as options for {@link #setTaskFragmentOperation}. */
+ @Nullable
+ private TaskFragmentOperation mTaskFragmentOperation;
+
@Nullable
private PendingIntent mPendingIntent;
@@ -1424,6 +1453,7 @@ public final class WindowContainerTransaction implements Parcelable {
mLaunchOptions = copy.mLaunchOptions;
mActivityIntent = copy.mActivityIntent;
mTaskFragmentCreationOptions = copy.mTaskFragmentCreationOptions;
+ mTaskFragmentOperation = copy.mTaskFragmentOperation;
mPendingIntent = copy.mPendingIntent;
mShortcutInfo = copy.mShortcutInfo;
mAlwaysOnTop = copy.mAlwaysOnTop;
@@ -1447,6 +1477,7 @@ public final class WindowContainerTransaction implements Parcelable {
mLaunchOptions = in.readBundle();
mActivityIntent = in.readTypedObject(Intent.CREATOR);
mTaskFragmentCreationOptions = in.readTypedObject(TaskFragmentCreationParams.CREATOR);
+ mTaskFragmentOperation = in.readTypedObject(TaskFragmentOperation.CREATOR);
mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
mShortcutInfo = in.readTypedObject(ShortcutInfo.CREATOR);
mAlwaysOnTop = in.readBoolean();
@@ -1535,6 +1566,11 @@ public final class WindowContainerTransaction implements Parcelable {
}
@Nullable
+ public TaskFragmentOperation getTaskFragmentOperation() {
+ return mTaskFragmentOperation;
+ }
+
+ @Nullable
public PendingIntent getPendingIntent() {
return mPendingIntent;
}
@@ -1612,6 +1648,9 @@ public final class WindowContainerTransaction implements Parcelable {
case HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH:
return "{setReparentLeafTaskIfRelaunch: container= " + mContainer
+ " reparentLeafTaskIfRelaunch= " + mReparentLeafTaskIfRelaunch + "}";
+ case HIERARCHY_OP_TYPE_SET_TASK_FRAGMENT_OPERATION:
+ return "{setTaskFragmentOperation: fragmentToken= " + mContainer
+ + " operation= " + mTaskFragmentOperation + "}";
default:
return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent
+ " mToTop=" + mToTop
@@ -1639,6 +1678,7 @@ public final class WindowContainerTransaction implements Parcelable {
dest.writeBundle(mLaunchOptions);
dest.writeTypedObject(mActivityIntent, flags);
dest.writeTypedObject(mTaskFragmentCreationOptions, flags);
+ dest.writeTypedObject(mTaskFragmentOperation, flags);
dest.writeTypedObject(mPendingIntent, flags);
dest.writeTypedObject(mShortcutInfo, flags);
dest.writeBoolean(mAlwaysOnTop);
@@ -1696,6 +1736,9 @@ public final class WindowContainerTransaction implements Parcelable {
private TaskFragmentCreationParams mTaskFragmentCreationOptions;
@Nullable
+ private TaskFragmentOperation mTaskFragmentOperation;
+
+ @Nullable
private PendingIntent mPendingIntent;
@Nullable
@@ -1775,6 +1818,12 @@ public final class WindowContainerTransaction implements Parcelable {
return this;
}
+ Builder setTaskFragmentOperation(
+ @Nullable TaskFragmentOperation taskFragmentOperation) {
+ mTaskFragmentOperation = taskFragmentOperation;
+ return this;
+ }
+
Builder setReparentLeafTaskIfRelaunch(boolean reparentLeafTaskIfRelaunch) {
mReparentLeafTaskIfRelaunch = reparentLeafTaskIfRelaunch;
return this;
@@ -1804,6 +1853,7 @@ public final class WindowContainerTransaction implements Parcelable {
hierarchyOp.mPendingIntent = mPendingIntent;
hierarchyOp.mAlwaysOnTop = mAlwaysOnTop;
hierarchyOp.mTaskFragmentCreationOptions = mTaskFragmentCreationOptions;
+ hierarchyOp.mTaskFragmentOperation = mTaskFragmentOperation;
hierarchyOp.mShortcutInfo = mShortcutInfo;
hierarchyOp.mReparentLeafTaskIfRelaunch = mReparentLeafTaskIfRelaunch;
diff --git a/core/java/android/window/WindowMetricsController.java b/core/java/android/window/WindowMetricsController.java
index 5b08879ae266..06449d519f68 100644
--- a/core/java/android/window/WindowMetricsController.java
+++ b/core/java/android/window/WindowMetricsController.java
@@ -24,8 +24,10 @@ import android.annotation.NonNull;
import android.app.ResourcesManager;
import android.app.WindowConfiguration;
import android.content.Context;
+import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.os.IBinder;
import android.os.RemoteException;
import android.util.DisplayMetrics;
import android.view.Display;
@@ -89,23 +91,16 @@ public final class WindowMetricsController {
isScreenRound = config.isScreenRound();
windowingMode = winConfig.getWindowingMode();
}
- final WindowInsets windowInsets = computeWindowInsets(bounds, isScreenRound, windowingMode);
+ final IBinder token = Context.getToken(mContext);
+ final WindowInsets windowInsets = getWindowInsetsFromServerForCurrentDisplay(token,
+ bounds, isScreenRound, windowingMode);
return new WindowMetrics(bounds, windowInsets, density);
}
- private WindowInsets computeWindowInsets(Rect bounds, boolean isScreenRound,
- @WindowConfiguration.WindowingMode int windowingMode) {
- // Initialize params which used for obtaining all system insets.
- final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
- params.token = Context.getToken(mContext);
- return getWindowInsetsFromServerForCurrentDisplay(params, bounds, isScreenRound,
- windowingMode);
- }
-
private WindowInsets getWindowInsetsFromServerForCurrentDisplay(
- WindowManager.LayoutParams attrs, Rect bounds, boolean isScreenRound,
+ IBinder token, Rect bounds, boolean isScreenRound,
@WindowConfiguration.WindowingMode int windowingMode) {
- return getWindowInsetsFromServerForDisplay(mContext.getDisplayId(), attrs, bounds,
+ return getWindowInsetsFromServerForDisplay(mContext.getDisplayId(), token, bounds,
isScreenRound, windowingMode);
}
@@ -113,22 +108,26 @@ public final class WindowMetricsController {
* Retrieves WindowInsets for the given context and display, given the window bounds.
*
* @param displayId the ID of the logical display to calculate insets for
- * @param attrs the LayoutParams for the calling app
+ * @param token the token of Activity or WindowContext
* @param bounds the window bounds to calculate insets for
* @param isScreenRound if the display identified by displayId is round
* @param windowingMode the windowing mode of the window to calculate insets for
* @return WindowInsets calculated for the given window bounds, on the given display
*/
- private static WindowInsets getWindowInsetsFromServerForDisplay(int displayId,
- WindowManager.LayoutParams attrs, Rect bounds, boolean isScreenRound,
- int windowingMode) {
+ private static WindowInsets getWindowInsetsFromServerForDisplay(int displayId, IBinder token,
+ Rect bounds, boolean isScreenRound, int windowingMode) {
try {
final InsetsState insetsState = new InsetsState();
final boolean alwaysConsumeSystemBars = WindowManagerGlobal.getWindowManagerService()
- .getWindowInsets(attrs, displayId, insetsState);
- return insetsState.calculateInsets(bounds, null /* ignoringVisibilityState*/,
- isScreenRound, alwaysConsumeSystemBars, SOFT_INPUT_ADJUST_NOTHING, attrs.flags,
- SYSTEM_UI_FLAG_VISIBLE, attrs.type, windowingMode,
+ .getWindowInsets(displayId, token, insetsState);
+ final float overrideInvScale = CompatibilityInfo.getOverrideInvertedScale();
+ if (overrideInvScale != 1f) {
+ insetsState.scale(overrideInvScale);
+ }
+ return insetsState.calculateInsets(bounds, null /* ignoringVisibilityState */,
+ isScreenRound, alwaysConsumeSystemBars, SOFT_INPUT_ADJUST_NOTHING,
+ 0 /* flags */, SYSTEM_UI_FLAG_VISIBLE,
+ WindowManager.LayoutParams.INVALID_WINDOW_TYPE, windowingMode,
null /* typeSideMap */);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -149,7 +148,6 @@ public final class WindowMetricsController {
Set<WindowMetrics> maxMetrics = new HashSet<>();
WindowInsets windowInsets;
DisplayInfo currentDisplayInfo;
- final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
for (int i = 0; i < possibleDisplayInfos.size(); i++) {
currentDisplayInfo = possibleDisplayInfos.get(i);
@@ -162,7 +160,7 @@ public final class WindowMetricsController {
// Initialize insets based upon display rotation. Note any window-provided insets
// will not be set.
windowInsets = getWindowInsetsFromServerForDisplay(
- currentDisplayInfo.displayId, params,
+ currentDisplayInfo.displayId, null /* token */,
new Rect(0, 0, currentDisplayInfo.getNaturalWidth(),
currentDisplayInfo.getNaturalHeight()), isScreenRound,
WINDOWING_MODE_FULLSCREEN);
diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp
index 02f6a77828c6..7002d9b4c489 100644
--- a/core/jni/android_view_InputDevice.cpp
+++ b/core/jni/android_view_InputDevice.cpp
@@ -78,11 +78,10 @@ jobject android_view_InputDevice_create(JNIEnv* env, const InputDeviceInfo& devi
static_cast<int32_t>(ident.product), descriptorObj.get(),
deviceInfo.isExternal(), deviceInfo.getSources(),
deviceInfo.getKeyboardType(), kcmObj.get(),
- deviceInfo.getCountryCode(), keyboardLanguageTagObj.get(),
- keyboardLayoutTypeObj.get(), deviceInfo.hasVibrator(),
- deviceInfo.hasMic(), deviceInfo.hasButtonUnderPad(),
- deviceInfo.hasSensor(), deviceInfo.hasBattery(),
- deviceInfo.supportsUsi()));
+ keyboardLanguageTagObj.get(), keyboardLayoutTypeObj.get(),
+ deviceInfo.hasVibrator(), deviceInfo.hasMic(),
+ deviceInfo.hasButtonUnderPad(), deviceInfo.hasSensor(),
+ deviceInfo.hasBattery(), deviceInfo.supportsUsi()));
// Note: We do not populate the Bluetooth address into the InputDevice object to avoid leaking
// it to apps that do not have the Bluetooth permission.
@@ -106,7 +105,7 @@ int register_android_view_InputDevice(JNIEnv* env)
gInputDeviceClassInfo.ctor = GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "<init>",
"(IIILjava/lang/String;IILjava/lang/"
- "String;ZIILandroid/view/KeyCharacterMap;ILjava/"
+ "String;ZIILandroid/view/KeyCharacterMap;Ljava/"
"lang/String;Ljava/lang/String;ZZZZZZ)V");
gInputDeviceClassInfo.addMotionRange = GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz,
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index a7c48f3a4e8f..bfa530143380 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3121,6 +3121,34 @@
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS"
android:protectionLevel="signature|role" />
+ <!-- Allows an application to manage date and time device policy. -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_TIME"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set device policies outside the current user
+ that are critical for securing data within the current user.
+ <p>Holding this permission allows the use of other held MANAGE_DEVICE_POLICY_*
+ permissions across all users on the device provided they are required for securing data
+ within the current user.-->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set device policies outside the current user
+ that are required for securing device ownership without accessing user data.
+ <p>Holding this permission allows the use of other held MANAGE_DEVICE_POLICY_*
+ permissions across all users on the device provided they do not grant access to user
+ data. -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set device policies outside the current user.
+ <p>Fuller form of {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS}
+ that removes the restriction on accessing user data.
+ <p>Holding this permission allows the use of any other held MANAGE_DEVICE_POLICY_*
+ permissions across all users on the device.-->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL"
+ android:protectionLevel="internal|role" />
+
<!-- @SystemApi @hide Allows an application to set a device owner on retail demo devices.-->
<permission android:name="android.permission.PROVISION_DEMO_DEVICE"
android:protectionLevel="signature|setup" />
@@ -6827,6 +6855,12 @@
<permission android:name="android.permission.REMAP_MODIFIER_KEYS"
android:protectionLevel="signature" />
+ <!-- Allows low-level access to monitor keyboard backlight changes.
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.MONITOR_KEYBOARD_BACKLIGHT"
+ android:protectionLevel="signature" />
+
<uses-permission android:name="android.permission.HANDLE_QUERY_PACKAGE_RESTART" />
<!-- Allows financed device kiosk apps to perform actions on the Device Lock service
@@ -6865,6 +6899,12 @@
<permission android:name="android.permission.GET_APP_METADATA"
android:protectionLevel="signature" />
+ <!-- @SystemApi Allows the holder to call health connect migration APIs.
+ @hide -->
+ <permission android:name="android.permission.MIGRATE_HEALTH_CONNECT_DATA"
+ android:protectionLevel="signature|knownSigner"
+ android:knownCerts="@array/config_healthConnectMigrationKnownSigners" />
+
<!-- Attribution for Geofencing service. -->
<attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
<!-- Attribution for Country Detector. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 23103679f440..a4d6fdd28054 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5245,6 +5245,14 @@
having a separating hinge. -->
<bool name="config_isDisplayHingeAlwaysSeparating">false</bool>
+ <!-- Whether enabling rotation compat policy for immersive apps that prevents auto rotation
+ into non-optimal screen orientation while in fullscreen. This is needed because immersive
+ apps, such as games, are often not optimized for all orientations and can have a poor UX
+ when rotated. Additionally, some games rely on sensors for the gameplay so users can
+ trigger such rotations accidentally when auto rotation is on.
+ Applicable only if ignoreOrientationRequest is enabled. -->
+ <bool name="config_letterboxIsDisplayRotationImmersiveAppCompatPolicyEnabled">false</bool>
+
<!-- Aspect ratio of letterboxing for fixed orientation. Values <= 1.0 will be ignored.
Note: Activity min/max aspect ratio restrictions will still be respected.
Therefore this override can control the maximum screen area that can be occupied by
@@ -5360,6 +5368,11 @@
If given value is outside of this range, the option 0 (top) is assummed. -->
<integer name="config_letterboxDefaultPositionForTabletopModeReachability">0</integer>
+ <!-- Whether should ignore app requested orientation in response to an app
+ calling Activity#setRequestedOrientation. See
+ LetterboxUiController#shouldIgnoreRequestedOrientation for details. -->
+ <bool name="config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled">false</bool>
+
<!-- Whether displaying letterbox education is enabled for letterboxed fullscreen apps. -->
<bool name="config_letterboxIsEducationEnabled">false</bool>
@@ -6113,4 +6126,9 @@
<item>@string/config_mainDisplayShape</item>
<item>@string/config_secondaryDisplayShape</item>
</string-array>
+ <!-- Certificate digests for trusted apps that will be allowed to obtain the knownSigner Health
+ Connect Migration permissions. The digest should be computed over the DER encoding of the
+ trusted certificate using the SHA-256 digest algorithm. -->
+ <string-array name="config_healthConnectMigrationKnownSigners">
+ </string-array>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index af29b233ba83..cd39e590310b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -945,7 +945,6 @@
<java-symbol type="string" name="serviceErased" />
<java-symbol type="string" name="serviceNotProvisioned" />
<java-symbol type="string" name="serviceRegistered" />
- <java-symbol type="string" name="setup_autofill" />
<java-symbol type="string" name="share" />
<java-symbol type="string" name="shareactionprovider_share_with" />
<java-symbol type="string" name="shareactionprovider_share_with_application" />
@@ -4414,6 +4413,7 @@
<java-symbol type="dimen" name="controls_thumbnail_image_max_height" />
<java-symbol type="dimen" name="controls_thumbnail_image_max_width" />
+ <java-symbol type="bool" name="config_letterboxIsDisplayRotationImmersiveAppCompatPolicyEnabled" />
<java-symbol type="dimen" name="config_fixedOrientationLetterboxAspectRatio" />
<java-symbol type="dimen" name="config_letterboxBackgroundWallpaperBlurRadius" />
<java-symbol type="integer" name="config_letterboxActivityCornersRadius" />
@@ -4430,6 +4430,7 @@
<java-symbol type="integer" name="config_letterboxDefaultPositionForVerticalReachability" />
<java-symbol type="integer" name="config_letterboxDefaultPositionForBookModeReachability" />
<java-symbol type="integer" name="config_letterboxDefaultPositionForTabletopModeReachability" />
+ <java-symbol type="bool" name="config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled" />
<java-symbol type="bool" name="config_letterboxIsEducationEnabled" />
<java-symbol type="dimen" name="config_letterboxDefaultMinAspectRatioForUnresizableApps" />
<java-symbol type="bool" name="config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled" />
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index abbbb2f516b2..e164e08e66e6 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -228,6 +228,20 @@ public class ActivityThreadTest {
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
assertScreenScale(scale, activity, originalActivityConfig, originalActivityMetrics);
+
+ // Execute a local relaunch item with current scaled config (e.g. simulate recreate),
+ // the config should not be scaled again.
+ final Configuration currentConfig = activity.getResources().getConfiguration();
+ final ClientTransaction localTransaction =
+ newTransaction(activityThread, activity.getActivityToken());
+ localTransaction.addCallback(ActivityRelaunchItem.obtain(
+ null /* pendingResults */, null /* pendingIntents */, 0 /* configChanges */,
+ new MergedConfiguration(currentConfig, currentConfig),
+ true /* preserveWindow */));
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(
+ () -> activityThread.executeTransaction(localTransaction));
+
+ assertScreenScale(scale, activity, originalActivityConfig, originalActivityMetrics);
} finally {
CompatibilityInfo.setOverrideInvertedScale(originalScale);
InstrumentationRegistry.getInstrumentation().runOnMainSync(
diff --git a/core/tests/coretests/src/android/companion/virtual/OWNERS b/core/tests/coretests/src/android/companion/virtual/OWNERS
index 1a3e927a106f..2e475a9a2742 100644
--- a/core/tests/coretests/src/android/companion/virtual/OWNERS
+++ b/core/tests/coretests/src/android/companion/virtual/OWNERS
@@ -1,3 +1 @@
-set noparent
-
include /services/companion/java/com/android/server/companion/virtual/OWNERS \ No newline at end of file
diff --git a/core/tests/coretests/src/android/hardware/input/KeyboardBacklightListenerTest.kt b/core/tests/coretests/src/android/hardware/input/KeyboardBacklightListenerTest.kt
new file mode 100644
index 000000000000..91d19a19379d
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/input/KeyboardBacklightListenerTest.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input
+
+import android.os.Handler
+import android.os.HandlerExecutor
+import android.os.test.TestLooper
+import android.platform.test.annotations.Presubmit
+import com.android.server.testutils.any
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.doAnswer
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoJUnitRunner
+import java.util.concurrent.Executor
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.fail
+
+/**
+ * Tests for [InputManager.KeyboardBacklightListener].
+ *
+ * Build/Install/Run:
+ * atest FrameworksCoreTests:KeyboardBacklightListenerTest
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner::class)
+class KeyboardBacklightListenerTest {
+ @get:Rule
+ val rule = MockitoJUnit.rule()!!
+
+ private lateinit var testLooper: TestLooper
+ private var registeredListener: IKeyboardBacklightListener? = null
+ private lateinit var executor: Executor
+ private lateinit var inputManager: InputManager
+
+ @Mock
+ private lateinit var iInputManagerMock: IInputManager
+
+ @Before
+ fun setUp() {
+ testLooper = TestLooper()
+ executor = HandlerExecutor(Handler(testLooper.looper))
+ registeredListener = null
+ inputManager = InputManager.resetInstance(iInputManagerMock)
+
+ // Handle keyboard backlight listener registration.
+ doAnswer {
+ val listener = it.getArgument(0) as IKeyboardBacklightListener
+ if (registeredListener != null &&
+ registeredListener!!.asBinder() != listener.asBinder()) {
+ // There can only be one registered keyboard backlight listener per process.
+ fail("Trying to register a new listener when one already exists")
+ }
+ registeredListener = listener
+ null
+ }.`when`(iInputManagerMock).registerKeyboardBacklightListener(any())
+
+ // Handle keyboard backlight listener being unregistered.
+ doAnswer {
+ val listener = it.getArgument(0) as IKeyboardBacklightListener
+ if (registeredListener == null ||
+ registeredListener!!.asBinder() != listener.asBinder()) {
+ fail("Trying to unregister a listener that is not registered")
+ }
+ registeredListener = null
+ null
+ }.`when`(iInputManagerMock).unregisterKeyboardBacklightListener(any())
+ }
+
+ @After
+ fun tearDown() {
+ InputManager.clearInstance()
+ }
+
+ private fun notifyKeyboardBacklightChanged(
+ deviceId: Int,
+ brightnessLevel: Int,
+ maxBrightnessLevel: Int = 10,
+ isTriggeredByKeyPress: Boolean = true
+ ) {
+ registeredListener!!.onBrightnessChanged(deviceId, IKeyboardBacklightState().apply {
+ this.brightnessLevel = brightnessLevel
+ this.maxBrightnessLevel = maxBrightnessLevel
+ }, isTriggeredByKeyPress)
+ }
+
+ @Test
+ fun testListenerIsNotifiedCorrectly() {
+ var callbackCount = 0
+
+ // Add a keyboard backlight listener
+ inputManager.registerKeyboardBacklightListener(executor) {
+ deviceId: Int,
+ keyboardBacklightState: KeyboardBacklightState,
+ isTriggeredByKeyPress: Boolean ->
+ callbackCount++
+ assertEquals(1, deviceId)
+ assertEquals(2, keyboardBacklightState.brightnessLevel)
+ assertEquals(10, keyboardBacklightState.maxBrightnessLevel)
+ assertEquals(true, isTriggeredByKeyPress)
+ }
+
+ // Adding the listener should register the callback with InputManagerService.
+ assertNotNull(registeredListener)
+
+ // Notifying keyboard backlight change will notify the listener.
+ notifyKeyboardBacklightChanged(1 /*deviceId*/, 2 /* brightnessLevel */)
+ testLooper.dispatchNext()
+ assertEquals(1, callbackCount)
+ }
+
+ @Test
+ fun testMultipleListeners() {
+ // Set up two callbacks.
+ var callbackCount1 = 0
+ var callbackCount2 = 0
+ val callback1 = InputManager.KeyboardBacklightListener { _, _, _ -> callbackCount1++ }
+ val callback2 = InputManager.KeyboardBacklightListener { _, _, _ -> callbackCount2++ }
+
+ // Add both keyboard backlight listeners
+ inputManager.registerKeyboardBacklightListener(executor, callback1)
+ inputManager.registerKeyboardBacklightListener(executor, callback2)
+
+ // Adding the listeners should register the callback with InputManagerService.
+ assertNotNull(registeredListener)
+
+ // Notifying keyboard backlight change trigger the both callbacks.
+ notifyKeyboardBacklightChanged(1 /*deviceId*/, 1 /* brightnessLevel */)
+ testLooper.dispatchAll()
+ assertEquals(1, callbackCount1)
+ assertEquals(1, callbackCount2)
+
+ inputManager.unregisterKeyboardBacklightListener(callback2)
+ // Notifying keyboard backlight change should still trigger callback1.
+ notifyKeyboardBacklightChanged(1 /*deviceId*/, 2 /* brightnessLevel */)
+ testLooper.dispatchAll()
+ assertEquals(2, callbackCount1)
+
+ // Unregister all listeners, should remove registered listener from InputManagerService
+ inputManager.unregisterKeyboardBacklightListener(callback1)
+ assertNull(registeredListener)
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index b910287aa535..87fa63d7fe14 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -17,6 +17,7 @@
package androidx.window.extensions.embedding;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior;
import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior;
@@ -31,8 +32,10 @@ import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
import android.util.ArrayMap;
+import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentCreationParams;
import android.window.TaskFragmentInfo;
+import android.window.TaskFragmentOperation;
import android.window.TaskFragmentOrganizer;
import android.window.TaskFragmentTransaction;
import android.window.WindowContainerTransaction;
@@ -114,13 +117,14 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
* @param activityIntent Intent to start the secondary Activity with.
* @param activityOptions ActivityOptions to start the secondary Activity with.
* @param windowingMode the windowing mode to set for the TaskFragments.
+ * @param splitAttributes the {@link SplitAttributes} to represent the split.
*/
void startActivityToSide(@NonNull WindowContainerTransaction wct,
@NonNull IBinder launchingFragmentToken, @NonNull Rect launchingFragmentBounds,
@NonNull Activity launchingActivity, @NonNull IBinder secondaryFragmentToken,
@NonNull Rect secondaryFragmentBounds, @NonNull Intent activityIntent,
@Nullable Bundle activityOptions, @NonNull SplitRule rule,
- @WindowingMode int windowingMode) {
+ @WindowingMode int windowingMode, @NonNull SplitAttributes splitAttributes) {
final IBinder ownerToken = launchingActivity.getActivityToken();
// Create or resize the launching TaskFragment.
@@ -131,6 +135,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
createTaskFragmentAndReparentActivity(wct, launchingFragmentToken, ownerToken,
launchingFragmentBounds, windowingMode, launchingActivity);
}
+ updateAnimationParams(wct, launchingFragmentToken, splitAttributes);
// Create a TaskFragment for the secondary activity.
final TaskFragmentCreationParams fragmentOptions = new TaskFragmentCreationParams.Builder(
@@ -144,6 +149,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
.setPairedPrimaryFragmentToken(launchingFragmentToken)
.build();
createTaskFragment(wct, fragmentOptions);
+ updateAnimationParams(wct, secondaryFragmentToken, splitAttributes);
wct.startActivityInTaskFragment(secondaryFragmentToken, ownerToken, activityIntent,
activityOptions);
@@ -163,6 +169,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
resizeTaskFragment(wct, fragmentToken, new Rect());
setAdjacentTaskFragments(wct, fragmentToken, null /* secondary */, null /* splitRule */);
updateWindowingMode(wct, fragmentToken, WINDOWING_MODE_UNDEFINED);
+ updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT);
}
/**
@@ -175,6 +182,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
createTaskFragmentAndReparentActivity(
wct, fragmentToken, activity.getActivityToken(), new Rect(),
WINDOWING_MODE_UNDEFINED, activity);
+ updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT);
}
/**
@@ -270,6 +278,24 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
wct.setWindowingMode(mFragmentInfos.get(fragmentToken).getToken(), windowingMode);
}
+ /**
+ * Updates the {@link TaskFragmentAnimationParams} for the given TaskFragment based on
+ * {@link SplitAttributes}.
+ */
+ void updateAnimationParams(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder fragmentToken, @NonNull SplitAttributes splitAttributes) {
+ updateAnimationParams(wct, fragmentToken, createAnimationParamsOrDefault(splitAttributes));
+ }
+
+ void updateAnimationParams(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder fragmentToken, @NonNull TaskFragmentAnimationParams animationParams) {
+ final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+ OP_TYPE_SET_ANIMATION_PARAMS)
+ .setAnimationParams(animationParams)
+ .build();
+ wct.setTaskFragmentOperation(fragmentToken, operation);
+ }
+
void deleteTaskFragment(@NonNull WindowContainerTransaction wct,
@NonNull IBinder fragmentToken) {
if (!mFragmentInfos.containsKey(fragmentToken)) {
@@ -291,4 +317,14 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) {
mCallback.onTransactionReady(transaction);
}
+
+ private static TaskFragmentAnimationParams createAnimationParamsOrDefault(
+ @Nullable SplitAttributes splitAttributes) {
+ if (splitAttributes == null) {
+ return TaskFragmentAnimationParams.DEFAULT;
+ }
+ return new TaskFragmentAnimationParams.Builder()
+ .setAnimationBackgroundColor(splitAttributes.getAnimationBackgroundColor())
+ .build();
+ }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index ce7d695beb2a..1e004a722cef 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -65,6 +65,7 @@ import android.util.Pair;
import android.util.Size;
import android.util.SparseArray;
import android.view.WindowMetrics;
+import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentParentInfo;
import android.window.TaskFragmentTransaction;
@@ -1157,6 +1158,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
taskId);
mPresenter.createTaskFragment(wct, expandedContainer.getTaskFragmentToken(),
activityInTask.getActivityToken(), new Rect(), WINDOWING_MODE_UNDEFINED);
+ mPresenter.updateAnimationParams(wct, expandedContainer.getTaskFragmentToken(),
+ TaskFragmentAnimationParams.DEFAULT);
return expandedContainer;
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 9db9f8788190..7b2af4933e66 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -36,6 +36,7 @@ import android.util.Size;
import android.view.View;
import android.view.WindowInsets;
import android.view.WindowMetrics;
+import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentCreationParams;
import android.window.WindowContainerTransaction;
@@ -176,7 +177,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties,
splitAttributes);
final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
- primaryActivity, primaryRectBounds, null);
+ primaryActivity, primaryRectBounds, splitAttributes, null /* containerToAvoid */);
// Create new empty task fragment
final int taskId = primaryContainer.getTaskId();
@@ -189,6 +190,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(),
primaryActivity.getActivityToken(), secondaryRectBounds,
windowingMode);
+ updateAnimationParams(wct, secondaryContainer.getTaskFragmentToken(), splitAttributes);
// Set adjacent to each other so that the containers below will be invisible.
setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule,
@@ -222,7 +224,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties,
splitAttributes);
final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
- primaryActivity, primaryRectBounds, null);
+ primaryActivity, primaryRectBounds, splitAttributes, null /* containerToAvoid */);
final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties,
splitAttributes);
@@ -236,7 +238,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
containerToAvoid = curSecondaryContainer;
}
final TaskFragmentContainer secondaryContainer = prepareContainerForActivity(wct,
- secondaryActivity, secondaryRectBounds, containerToAvoid);
+ secondaryActivity, secondaryRectBounds, splitAttributes, containerToAvoid);
// Set adjacent to each other so that the containers below will be invisible.
setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule,
@@ -253,7 +255,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
*/
private TaskFragmentContainer prepareContainerForActivity(
@NonNull WindowContainerTransaction wct, @NonNull Activity activity,
- @NonNull Rect bounds, @Nullable TaskFragmentContainer containerToAvoid) {
+ @NonNull Rect bounds, @NonNull SplitAttributes splitAttributes,
+ @Nullable TaskFragmentContainer containerToAvoid) {
TaskFragmentContainer container = mController.getContainerWithActivity(activity);
final int taskId = container != null ? container.getTaskId() : activity.getTaskId();
if (container == null || container == containerToAvoid) {
@@ -270,6 +273,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
.getWindowingModeForSplitTaskFragment(bounds);
updateTaskFragmentWindowingModeIfRegistered(wct, container, windowingMode);
}
+ updateAnimationParams(wct, container.getTaskFragmentToken(), splitAttributes);
return container;
}
@@ -314,7 +318,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
rule, splitAttributes);
startActivityToSide(wct, primaryContainer.getTaskFragmentToken(), primaryRectBounds,
launchingActivity, secondaryContainer.getTaskFragmentToken(), secondaryRectBounds,
- activityIntent, activityOptions, rule, windowingMode);
+ activityIntent, activityOptions, rule, windowingMode, splitAttributes);
if (isPlaceholder) {
// When placeholder is launched in split, we should keep the focus on the primary.
wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken());
@@ -365,6 +369,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
primaryRectBounds);
updateTaskFragmentWindowingModeIfRegistered(wct, primaryContainer, windowingMode);
updateTaskFragmentWindowingModeIfRegistered(wct, secondaryContainer, windowingMode);
+ updateAnimationParams(wct, primaryContainer.getTaskFragmentToken(), splitAttributes);
+ updateAnimationParams(wct, secondaryContainer.getTaskFragmentToken(), splitAttributes);
}
private void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
@@ -459,6 +465,24 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
super.updateWindowingMode(wct, fragmentToken, windowingMode);
}
+ @Override
+ void updateAnimationParams(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder fragmentToken, @NonNull TaskFragmentAnimationParams animationParams) {
+ final TaskFragmentContainer container = mController.getContainer(fragmentToken);
+ if (container == null) {
+ throw new IllegalStateException("Setting animation params for a task fragment that is"
+ + " not registered with controller.");
+ }
+
+ if (container.areLastRequestedAnimationParamsEqual(animationParams)) {
+ // Return early if the animation params were already requested
+ return;
+ }
+
+ container.setLastRequestAnimationParams(animationParams);
+ super.updateAnimationParams(wct, fragmentToken, animationParams);
+ }
+
/**
* Expands the split container if the current split bounds are smaller than the Activity or
* Intent that is added to the container.
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 6bfdfe7593b8..076856c373d6 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -26,6 +26,7 @@ import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
import android.util.Size;
+import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentInfo;
import android.window.WindowContainerTransaction;
@@ -108,6 +109,13 @@ class TaskFragmentContainer {
private int mLastRequestedWindowingMode = WINDOWING_MODE_UNDEFINED;
/**
+ * TaskFragmentAnimationParams that was requested last via
+ * {@link android.window.WindowContainerTransaction}.
+ */
+ @NonNull
+ private TaskFragmentAnimationParams mLastAnimationParams = TaskFragmentAnimationParams.DEFAULT;
+
+ /**
* When the TaskFragment has appeared in server, but is empty, we should remove the TaskFragment
* if it is still empty after the timeout.
*/
@@ -560,6 +568,21 @@ class TaskFragmentContainer {
mLastRequestedWindowingMode = windowingModes;
}
+ /**
+ * Checks if last requested {@link TaskFragmentAnimationParams} are equal to the provided value.
+ */
+ boolean areLastRequestedAnimationParamsEqual(
+ @NonNull TaskFragmentAnimationParams animationParams) {
+ return mLastAnimationParams.equals(animationParams);
+ }
+
+ /**
+ * Updates the last requested {@link TaskFragmentAnimationParams}.
+ */
+ void setLastRequestAnimationParams(@NonNull TaskFragmentAnimationParams animationParams) {
+ mLastAnimationParams = animationParams;
+ }
+
/** Gets the parent leaf Task id. */
int getTaskId() {
return mTaskContainer.getTaskId();
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
index 13a2c78d463e..d189ae2cf72e 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
@@ -22,6 +22,7 @@ import android.platform.test.annotations.Presubmit;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import androidx.window.extensions.embedding.SplitAttributes;
import org.junit.Before;
import org.junit.Test;
@@ -53,4 +54,15 @@ public class WindowExtensionsTest {
public void testGetActivityEmbeddingComponent() {
assertThat(mExtensions.getActivityEmbeddingComponent()).isNotNull();
}
+
+ @Test
+ public void testSplitAttributes_default() {
+ // Make sure the default value in the extensions aar.
+ final SplitAttributes splitAttributes = new SplitAttributes.Builder().build();
+ assertThat(splitAttributes.getLayoutDirection())
+ .isEqualTo(SplitAttributes.LayoutDirection.LOCALE);
+ assertThat(splitAttributes.getSplitType())
+ .isEqualTo(new SplitAttributes.SplitType.RatioSplitType(0.5f));
+ assertThat(splitAttributes.getAnimationBackgroundColor()).isEqualTo(0);
+ }
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
index 6dae0a1086b3..fcd4d621e753 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
@@ -19,6 +19,7 @@ package androidx.window.extensions.embedding;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_PRIMARY_WITH_SECONDARY;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_SECONDARY_WITH_PRIMARY;
@@ -60,12 +61,15 @@ import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.graphics.Color;
import android.graphics.Rect;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import android.util.Pair;
import android.util.Size;
+import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentInfo;
+import android.window.TaskFragmentOperation;
import android.window.WindowContainerTransaction;
import androidx.test.core.app.ApplicationProvider;
@@ -163,7 +167,38 @@ public class SplitPresenterTest {
WINDOWING_MODE_MULTI_WINDOW);
verify(mTransaction, never()).setWindowingMode(any(), anyInt());
+ }
+
+ @Test
+ public void testUpdateAnimationParams() {
+ final TaskFragmentContainer container = mController.newContainer(mActivity, TASK_ID);
+
+ // Verify the default.
+ assertTrue(container.areLastRequestedAnimationParamsEqual(
+ TaskFragmentAnimationParams.DEFAULT));
+
+ final int bgColor = Color.GREEN;
+ final TaskFragmentAnimationParams animationParams =
+ new TaskFragmentAnimationParams.Builder()
+ .setAnimationBackgroundColor(bgColor)
+ .build();
+ mPresenter.updateAnimationParams(mTransaction, container.getTaskFragmentToken(),
+ animationParams);
+
+ final TaskFragmentOperation expectedOperation = new TaskFragmentOperation.Builder(
+ OP_TYPE_SET_ANIMATION_PARAMS)
+ .setAnimationParams(animationParams)
+ .build();
+ verify(mTransaction).setTaskFragmentOperation(container.getTaskFragmentToken(),
+ expectedOperation);
+ assertTrue(container.areLastRequestedAnimationParamsEqual(animationParams));
+
+ // No request to set the same animation params.
+ clearInvocations(mTransaction);
+ mPresenter.updateAnimationParams(mTransaction, container.getTaskFragmentToken(),
+ animationParams);
+ verify(mTransaction, never()).setTaskFragmentOperation(any(), any());
}
@Test
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index 4978e04e0115..84ab4487feee 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/libs/WindowManager/Shell/res/color/decor_caption_title_color.xml b/libs/WindowManager/Shell/res/color/decor_title_color.xml
index 1ecc13e4da38..1ecc13e4da38 100644
--- a/libs/WindowManager/Shell/res/color/decor_caption_title_color.xml
+++ b/libs/WindowManager/Shell/res/color/decor_title_color.xml
diff --git a/libs/WindowManager/Shell/res/drawable/decor_caption_menu_background.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml
index 416287d2cbb3..416287d2cbb3 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_caption_menu_background.xml
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml
diff --git a/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml
index 416287d2cbb3..416287d2cbb3 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml
diff --git a/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_decor_handle_menu.xml
index 582a11cfdb8e..8b4792acba3e 100644
--- a/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_decor_handle_menu.xml
@@ -20,7 +20,7 @@ android:id="@+id/handle_menu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
-android:background="@drawable/decor_caption_menu_background">
+android:background="@drawable/desktop_mode_decor_menu_background">
<Button
style="@style/CaptionButtonStyle"
android:id="@+id/fullscreen_button"
diff --git a/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml
index 51e634c17532..2a4cc02f0925 100644
--- a/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml
@@ -16,10 +16,10 @@
-->
<com.android.wm.shell.windowdecor.WindowDecorLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/caption"
+ android:id="@+id/desktop_mode_caption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:background="@drawable/decor_caption_title">
+ android:background="@drawable/desktop_mode_decor_title">
<Button
style="@style/CaptionButtonStyle"
android:id="@+id/back_button"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
index bd2ea9c1f822..94e01e96730c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
@@ -223,16 +223,6 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
mObscuredTouchRegion = obscuredRegion;
}
- private void onLocationChanged(WindowContainerTransaction wct) {
- // Update based on the screen bounds
- getBoundsOnScreen(mTmpRect);
- getRootView().getBoundsOnScreen(mTmpRootRect);
- if (!mTmpRootRect.contains(mTmpRect)) {
- mTmpRect.offsetTo(0, 0);
- }
- wct.setBounds(mTaskToken, mTmpRect);
- }
-
/**
* Call when view position or size has changed. Do not call when animating.
*/
@@ -245,10 +235,15 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
if (isUsingShellTransitions() && mTaskViewTransitions.hasPending()) return;
WindowContainerTransaction wct = new WindowContainerTransaction();
- onLocationChanged(wct);
+ updateWindowBounds(wct);
mSyncQueue.queue(wct);
}
+ private void updateWindowBounds(WindowContainerTransaction wct) {
+ getBoundsOnScreen(mTmpRect);
+ wct.setBounds(mTaskToken, mTmpRect);
+ }
+
/**
* Release this container if it is initialized.
*/
@@ -572,7 +567,7 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
.apply();
// TODO: determine if this is really necessary or not
- onLocationChanged(wct);
+ updateWindowBounds(wct);
} else {
// The surface has already been destroyed before the task has appeared,
// so go ahead and hide the task entirely
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index 7aae6335398a..d0aef2023048 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -16,6 +16,12 @@
package com.android.wm.shell.common;
+import static android.view.EventLogTags.IMF_IME_REMOTE_ANIM_CANCEL;
+import static android.view.EventLogTags.IMF_IME_REMOTE_ANIM_END;
+import static android.view.EventLogTags.IMF_IME_REMOTE_ANIM_START;
+import static android.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY;
+import static android.view.inputmethod.ImeTracker.TOKEN_NONE;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
@@ -26,6 +32,7 @@ import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.RemoteException;
+import android.util.EventLog;
import android.util.Slog;
import android.util.SparseArray;
import android.view.IDisplayWindowInsetsController;
@@ -47,6 +54,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.wm.shell.sysui.ShellInit;
import java.util.ArrayList;
+import java.util.Objects;
import java.util.concurrent.Executor;
/**
@@ -274,29 +282,30 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
if (hasImeSourceControl) {
- final Point lastSurfacePosition = mImeSourceControl != null
- ? mImeSourceControl.getSurfacePosition() : null;
- final boolean positionChanged =
- !imeSourceControl.getSurfacePosition().equals(lastSurfacePosition);
- final boolean leashChanged =
- !haveSameLeash(mImeSourceControl, imeSourceControl);
if (mAnimation != null) {
+ final Point lastSurfacePosition = hadImeSourceControl
+ ? mImeSourceControl.getSurfacePosition() : null;
+ final boolean positionChanged =
+ !imeSourceControl.getSurfacePosition().equals(lastSurfacePosition);
if (positionChanged) {
startAnimation(mImeShowing, true /* forceRestart */, null /* statsToken */);
}
} else {
- if (leashChanged) {
+ if (!haveSameLeash(mImeSourceControl, imeSourceControl)) {
applyVisibilityToLeash(imeSourceControl);
}
if (!mImeShowing) {
removeImeSurface();
}
- if (mImeSourceControl != null) {
- mImeSourceControl.release(SurfaceControl::release);
- }
}
- mImeSourceControl = imeSourceControl;
+ } else if (mAnimation != null) {
+ mAnimation.cancel();
+ }
+
+ if (hadImeSourceControl && mImeSourceControl != imeSourceControl) {
+ mImeSourceControl.release(SurfaceControl::release);
}
+ mImeSourceControl = imeSourceControl;
}
private void applyVisibilityToLeash(InsetsSourceControl imeSourceControl) {
@@ -469,6 +478,15 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
ImeTracker.PHASE_WM_ANIMATION_RUNNING);
t.show(mImeSourceControl.getLeash());
}
+ if (DEBUG_IME_VISIBILITY) {
+ EventLog.writeEvent(IMF_IME_REMOTE_ANIM_START,
+ statsToken != null ? statsToken.getTag() : TOKEN_NONE,
+ mDisplayId, mAnimationDirection, alpha, startY , endY,
+ Objects.toString(mImeSourceControl.getLeash()),
+ Objects.toString(mImeSourceControl.getInsetsHint()),
+ Objects.toString(mImeSourceControl.getSurfacePosition()),
+ Objects.toString(mImeFrame));
+ }
t.apply();
mTransactionPool.release(t);
}
@@ -476,6 +494,11 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
@Override
public void onAnimationCancel(Animator animation) {
mCancelled = true;
+ if (DEBUG_IME_VISIBILITY) {
+ EventLog.writeEvent(IMF_IME_REMOTE_ANIM_CANCEL,
+ statsToken != null ? statsToken.getTag() : TOKEN_NONE, mDisplayId,
+ Objects.toString(mImeSourceControl.getInsetsHint()));
+ }
}
@Override
@@ -499,6 +522,15 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
ImeTracker.get().onCancelled(mStatsToken,
ImeTracker.PHASE_WM_ANIMATION_RUNNING);
}
+ if (DEBUG_IME_VISIBILITY) {
+ EventLog.writeEvent(IMF_IME_REMOTE_ANIM_END,
+ statsToken != null ? statsToken.getTag() : TOKEN_NONE,
+ mDisplayId, mAnimationDirection, endY,
+ Objects.toString(mImeSourceControl.getLeash()),
+ Objects.toString(mImeSourceControl.getInsetsHint()),
+ Objects.toString(mImeSourceControl.getSurfacePosition()),
+ Objects.toString(mImeFrame));
+ }
t.apply();
mTransactionPool.release(t);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index 65da757b1396..a05ed4f24a08 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -111,7 +111,7 @@ public class TaskSnapshotWindow {
final SurfaceControl surfaceControl = new SurfaceControl();
final ClientWindowFrames tmpFrames = new ClientWindowFrames();
- final InsetsSourceControl[] tmpControls = new InsetsSourceControl[0];
+ final InsetsSourceControl.Array tmpControls = new InsetsSourceControl.Array();
final MergedConfiguration tmpMergedConfiguration = new MergedConfiguration();
final TaskDescription taskDescription;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 039f0e3b5917..fc2a828fb263 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -374,8 +374,6 @@ public class Transitions implements RemoteCallable<Transitions> {
// If this is a transferred starting window, we want it immediately visible.
&& (change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) == 0) {
t.setAlpha(leash, 0.f);
- // fix alpha in finish transaction in case the animator itself no-ops.
- finishT.setAlpha(leash, 1.f);
}
} else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
finishT.hide(leash);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index b500f5fb0155..b4301577ac10 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -282,7 +282,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
public boolean onTouch(View v, MotionEvent e) {
boolean isDrag = false;
int id = v.getId();
- if (id != R.id.caption_handle && id != R.id.caption) {
+ if (id != R.id.caption_handle && id != R.id.desktop_mode_caption) {
return false;
}
if (id == R.id.caption_handle) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 467f374f2110..9c2beb9c4b2b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -132,7 +132,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mRelayoutParams.reset();
mRelayoutParams.mRunningTaskInfo = taskInfo;
- mRelayoutParams.mLayoutResId = R.layout.caption_window_decoration;
+ mRelayoutParams.mLayoutResId = R.layout.desktop_mode_window_decor;
mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
mRelayoutParams.mCaptionWidthId = R.dimen.freeform_decor_caption_width;
mRelayoutParams.mShadowRadiusId = shadowRadiusID;
@@ -212,7 +212,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
* Sets up listeners when a new root view is created.
*/
private void setupRootView() {
- View caption = mResult.mRootView.findViewById(R.id.caption);
+ View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
caption.setOnTouchListener(mOnCaptionTouchListener);
View close = caption.findViewById(R.id.close_window);
close.setOnClickListener(mOnCaptionButtonClickListener);
@@ -243,7 +243,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
*/
private void setCaptionVisibility(boolean visible) {
int v = visible ? View.VISIBLE : View.GONE;
- View captionView = mResult.mRootView.findViewById(R.id.caption);
+ View captionView = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
captionView.setVisibility(v);
if (!visible) closeHandleMenu();
}
@@ -265,7 +265,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
*/
void setButtonVisibility(boolean visible) {
int visibility = visible ? View.VISIBLE : View.GONE;
- View caption = mResult.mRootView.findViewById(R.id.caption);
+ View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
View back = caption.findViewById(R.id.back_button);
View close = caption.findViewById(R.id.close_window);
back.setVisibility(visibility);
@@ -304,7 +304,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId);
int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
String namePrefix = "Caption Menu";
- mHandleMenu = addWindow(R.layout.caption_handle_menu, namePrefix, t,
+ mHandleMenu = addWindow(R.layout.desktop_mode_decor_handle_menu, namePrefix, t,
x - mResult.mDecorContainerOffsetX, y - mResult.mDecorContainerOffsetY,
width, height);
mSyncQueue.runInSync(transaction -> {
@@ -336,7 +336,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
*/
void closeHandleMenuIfNeeded(MotionEvent ev) {
if (isHandleMenuActive()) {
- if (!checkEventInCaptionView(ev, R.id.caption)) {
+ if (!checkEventInCaptionView(ev, R.id.desktop_mode_caption)) {
closeHandleMenu();
}
}
@@ -389,7 +389,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
*/
void checkClickEvent(MotionEvent ev) {
if (mResult.mRootView == null) return;
- View caption = mResult.mRootView.findViewById(R.id.caption);
+ View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
PointF inputPoint = offsetCaptionLocation(ev);
if (!isHandleMenuActive()) {
View handle = caption.findViewById(R.id.caption_handle);
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
index 04b1bdd3fd46..08ed91b3cab1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
@@ -26,6 +26,9 @@ import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.navBarLayerIsVisibleAtEnd
+import com.android.server.wm.flicker.navBarLayerPositionAtEnd
+import org.junit.Assume
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@@ -91,6 +94,10 @@ class LaunchBubbleFromLockScreen(flicker: FlickerTest) : BaseBubbleScreen(flicke
flicker.assertLayersEnd { this.isVisible(testApp) }
}
+ @Postsubmit @Test fun navBarLayerIsVisibleAtEnd() = flicker.navBarLayerIsVisibleAtEnd()
+
+ @Postsubmit @Test fun navBarLayerPositionAtEnd() = flicker.navBarLayerPositionAtEnd()
+
/** {@inheritDoc} */
@FlakyTest
@Test
@@ -98,19 +105,28 @@ class LaunchBubbleFromLockScreen(flicker: FlickerTest) : BaseBubbleScreen(flicke
super.visibleLayersShownMoreThanOneConsecutiveEntry()
/** {@inheritDoc} */
- @FlakyTest(bugId = 206753786)
+ @Postsubmit
@Test
- override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
+ override fun navBarLayerIsVisibleAtStartAndEnd() {
+ Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
+ super.navBarLayerIsVisibleAtStartAndEnd()
+ }
/** {@inheritDoc} */
- @FlakyTest(bugId = 206753786)
+ @Postsubmit
@Test
- override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+ override fun navBarLayerPositionAtStartAndEnd() {
+ Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
+ super.navBarLayerPositionAtStartAndEnd()
+ }
/** {@inheritDoc} */
- @FlakyTest(bugId = 206753786)
+ @Postsubmit
@Test
- override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+ override fun navBarWindowIsAlwaysVisible() {
+ Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
+ super.navBarWindowIsAlwaysVisible()
+ }
/** {@inheritDoc} */
@FlakyTest(bugId = 242088970)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
index 9b4e39c42d11..b69ff6451d1c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
@@ -16,7 +16,7 @@
package com.android.wm.shell.flicker.bubble
-import android.platform.test.annotations.RequiresDevice
+import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.FlickerBuilder
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
index e9847fae00fe..16acc11f5729 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
@@ -17,7 +17,7 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import android.platform.test.annotations.RequiresDevice
+import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.FlickerTestFactory
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt
index a16f5f6f1620..2cb18f948f0e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt
@@ -24,10 +24,7 @@ import androidx.test.uiautomator.UiDevice
import org.junit.Before
import org.junit.runners.Parameterized
-abstract class PipTestBase(
- protected val rotationName: String,
- protected val rotation: Int
-) {
+abstract class PipTestBase(protected val rotationName: String, protected val rotation: Int) {
val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
val uiDevice = UiDevice.getInstance(instrumentation)
val packageManager: PackageManager = instrumentation.context.packageManager
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
index 73671dbb3c6f..fcdad960107f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -93,7 +93,8 @@ class DragDividerToResize(flicker: FlickerTest) : SplitScreenBase(flicker) {
}
@FlakyTest(bugId = 263213649)
- @Test fun primaryAppLayerKeepVisible_ShellTransit() {
+ @Test
+ fun primaryAppLayerKeepVisible_ShellTransit() {
Assume.assumeTrue(isShellTransitionsEnabled)
flicker.layerKeepVisible(primaryApp)
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index 22df362a6ed3..ffb1a4d66f1e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -22,6 +22,8 @@ import static android.view.Surface.ROTATION_0;
import static android.view.WindowInsets.Type.ime;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
@@ -124,6 +126,15 @@ public class DisplayImeControllerTest extends ShellTestCase {
verify(mT).show(any());
}
+ @Test
+ public void insetsControlChanged_updateImeSourceControl() {
+ mPerDisplay.insetsControlChanged(insetsStateWithIme(false), insetsSourceControl());
+ assertNotNull(mPerDisplay.mImeSourceControl);
+
+ mPerDisplay.insetsControlChanged(new InsetsState(), new InsetsSourceControl[]{});
+ assertNull(mPerDisplay.mImeSourceControl);
+ }
+
private InsetsSourceControl[] insetsSourceControl() {
return new InsetsSourceControl[]{
new InsetsSourceControl(
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 d06fb55a5769..7ec4e21bcfcc 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
@@ -30,10 +30,13 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static java.lang.Integer.MAX_VALUE;
+
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.RemoteException;
import android.test.suitebuilder.annotation.SmallTest;
@@ -135,12 +138,12 @@ public class PipControllerTest extends ShellTestCase {
@Test
public void instantiatePipController_addInitCallback() {
- verify(mShellInit, times(1)).addInitCallback(any(), any());
+ verify(mShellInit, times(1)).addInitCallback(any(), eq(mPipController));
}
@Test
public void instantiateController_registerDumpCallback() {
- verify(mMockShellCommandHandler, times(1)).addDumpCallback(any(), any());
+ verify(mMockShellCommandHandler, times(1)).addDumpCallback(any(), eq(mPipController));
}
@Test
@@ -156,7 +159,7 @@ public class PipControllerTest extends ShellTestCase {
@Test
public void instantiatePipController_registerExternalInterface() {
verify(mShellController, times(1)).addExternalInterface(
- eq(ShellSharedConstants.KEY_EXTRA_SHELL_PIP), any(), any());
+ eq(ShellSharedConstants.KEY_EXTRA_SHELL_PIP), any(), eq(mPipController));
}
@Test
@@ -252,6 +255,10 @@ public class PipControllerTest extends ShellTestCase {
final int displayId = 1;
final Rect bounds = new Rect(0, 0, 10, 10);
when(mMockPipBoundsAlgorithm.getDefaultBounds()).thenReturn(bounds);
+ when(mMockPipBoundsState.getBounds()).thenReturn(bounds);
+ when(mMockPipBoundsState.getMinSize()).thenReturn(new Point(1, 1));
+ when(mMockPipBoundsState.getMaxSize()).thenReturn(new Point(MAX_VALUE, MAX_VALUE));
+ when(mMockPipBoundsState.getBounds()).thenReturn(bounds);
when(mMockPipBoundsState.getDisplayId()).thenReturn(displayId);
when(mMockPipBoundsState.getDisplayLayout()).thenReturn(mMockDisplayLayout1);
when(mMockDisplayController.getDisplayLayout(displayId)).thenReturn(mMockDisplayLayout2);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
index fbc50c68eff9..8d92d0864338 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
@@ -34,6 +34,7 @@ import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.ShellExecutor;
@@ -61,10 +62,9 @@ public class ShellControllerTest extends ShellTestCase {
@Mock
private ShellCommandHandler mShellCommandHandler;
@Mock
- private ShellExecutor mExecutor;
- @Mock
private Context mTestUserContext;
+ private TestShellExecutor mExecutor;
private ShellController mController;
private TestConfigurationChangeListener mConfigChangeListener;
private TestKeyguardChangeListener mKeyguardChangeListener;
@@ -77,6 +77,7 @@ public class ShellControllerTest extends ShellTestCase {
mKeyguardChangeListener = new TestKeyguardChangeListener();
mConfigChangeListener = new TestConfigurationChangeListener();
mUserChangeListener = new TestUserChangeListener();
+ mExecutor = new TestShellExecutor();
mController = new ShellController(mShellInit, mShellCommandHandler, mExecutor);
mController.onConfigurationChanged(getConfigurationCopy());
}
@@ -104,6 +105,7 @@ public class ShellControllerTest extends ShellTestCase {
Bundle b = new Bundle();
mController.asShell().createExternalInterfaces(b);
+ mExecutor.flushAll();
assertTrue(b.getIBinder(EXTRA_TEST_BINDER) == callback);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index c764741d4cd6..595c3b4880df 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -936,7 +936,7 @@ public class ShellTransitionTests extends ShellTestCase {
TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
RunningTaskInfo taskInfo) {
final TransitionInfo.Change change =
- new TransitionInfo.Change(null /* token */, null /* leash */);
+ new TransitionInfo.Change(null /* token */, createMockSurface(true));
change.setMode(mode);
change.setTaskInfo(taskInfo);
mInfo.addChange(change);
@@ -961,7 +961,7 @@ public class ShellTransitionTests extends ShellTestCase {
final TransitionInfo.Change mChange;
ChangeBuilder(@WindowManager.TransitionType int mode) {
- mChange = new TransitionInfo.Change(null /* token */, null /* leash */);
+ mChange = new TransitionInfo.Change(null /* token */, createMockSurface(true));
mChange.setMode(mode);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
index a5e3a2e76ce5..355072116cb1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
@@ -205,28 +205,32 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase {
"testEventReceiversOnMultipleDisplays", /*width=*/ 400, /*height=*/ 400,
/*densityDpi=*/ 320, surfaceView.getHolder().getSurface(),
DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
- int secondaryDisplayId = secondaryDisplay.getDisplay().getDisplayId();
-
- final int taskId = 1;
- final ActivityManager.RunningTaskInfo taskInfo =
- createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FREEFORM);
- final ActivityManager.RunningTaskInfo secondTaskInfo =
- createTaskInfo(taskId + 1, secondaryDisplayId, WINDOWING_MODE_FREEFORM);
- final ActivityManager.RunningTaskInfo thirdTaskInfo =
- createTaskInfo(taskId + 2, secondaryDisplayId, WINDOWING_MODE_FREEFORM);
-
- SurfaceControl surfaceControl = mock(SurfaceControl.class);
- final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
- final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
-
- mDesktopModeWindowDecorViewModel.onTaskOpening(taskInfo, surfaceControl, startT,
- finishT);
- mDesktopModeWindowDecorViewModel.onTaskOpening(secondTaskInfo, surfaceControl,
- startT, finishT);
- mDesktopModeWindowDecorViewModel.onTaskOpening(thirdTaskInfo, surfaceControl,
- startT, finishT);
- mDesktopModeWindowDecorViewModel.destroyWindowDecoration(thirdTaskInfo);
- mDesktopModeWindowDecorViewModel.destroyWindowDecoration(taskInfo);
+ try {
+ int secondaryDisplayId = secondaryDisplay.getDisplay().getDisplayId();
+
+ final int taskId = 1;
+ final ActivityManager.RunningTaskInfo taskInfo =
+ createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FREEFORM);
+ final ActivityManager.RunningTaskInfo secondTaskInfo =
+ createTaskInfo(taskId + 1, secondaryDisplayId, WINDOWING_MODE_FREEFORM);
+ final ActivityManager.RunningTaskInfo thirdTaskInfo =
+ createTaskInfo(taskId + 2, secondaryDisplayId, WINDOWING_MODE_FREEFORM);
+
+ SurfaceControl surfaceControl = mock(SurfaceControl.class);
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+
+ mDesktopModeWindowDecorViewModel.onTaskOpening(taskInfo, surfaceControl, startT,
+ finishT);
+ mDesktopModeWindowDecorViewModel.onTaskOpening(secondTaskInfo, surfaceControl,
+ startT, finishT);
+ mDesktopModeWindowDecorViewModel.onTaskOpening(thirdTaskInfo, surfaceControl,
+ startT, finishT);
+ mDesktopModeWindowDecorViewModel.destroyWindowDecoration(thirdTaskInfo);
+ mDesktopModeWindowDecorViewModel.destroyWindowDecoration(taskInfo);
+ } finally {
+ secondaryDisplay.release();
+ }
});
verify(mMockInputMonitorFactory, times(2)).create(any(), any());
verify(mInputMonitor, times(1)).dispose();
@@ -239,7 +243,7 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase {
r.run();
latch.countDown();
});
- latch.await(20, TimeUnit.MILLISECONDS);
+ latch.await(1, TimeUnit.SECONDS);
}
private static ActivityManager.RunningTaskInfo createTaskInfo(int taskId,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index dd9ab9899e13..ec4f17fd072b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -48,6 +48,7 @@ import android.view.SurfaceControlViewHost;
import android.view.View;
import android.view.ViewRootImpl;
import android.view.WindowManager.LayoutParams;
+import android.window.TaskConstants;
import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
@@ -232,7 +233,8 @@ public class WindowDecorationTests extends ShellTestCase {
verify(mMockSurfaceControlStartT)
.setColor(taskBackgroundSurface, new float[] {1.f, 1.f, 0.f});
verify(mMockSurfaceControlStartT).setShadowRadius(taskBackgroundSurface, 10);
- verify(mMockSurfaceControlStartT).setLayer(taskBackgroundSurface, -1);
+ verify(mMockSurfaceControlStartT).setLayer(taskBackgroundSurface,
+ TaskConstants.TASK_CHILD_LAYER_TASK_BACKGROUND);
verify(mMockSurfaceControlStartT).show(taskBackgroundSurface);
verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
@@ -560,7 +562,8 @@ public class WindowDecorationTests extends ShellTestCase {
int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
String name = "Test Window";
WindowDecoration.AdditionalWindow additionalWindow =
- addWindow(R.layout.caption_handle_menu, name, mMockSurfaceControlAddWindowT,
+ addWindow(R.layout.desktop_mode_decor_handle_menu, name,
+ mMockSurfaceControlAddWindowT,
x - mRelayoutResult.mDecorContainerOffsetX,
y - mRelayoutResult.mDecorContainerOffsetY,
width, height);
diff --git a/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp b/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp
index 2ec78a429481..138b3efd10ed 100644
--- a/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp
+++ b/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp
@@ -29,7 +29,7 @@ AHardwareBuffer* allocHardwareBuffer() {
.height = 16,
.layers = 1,
.format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
- .usage = AHARDWAREBUFFER_USAGE_CPU_READ_RARELY | AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY,
+ .usage = AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE,
};
constexpr int kSucceeded = 0;
int status = AHardwareBuffer_allocate(&desc, &buffer);
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index a84c42af93c2..761edf6c6170 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -5855,6 +5855,117 @@ public class AudioManager {
}
}
+ // Each listener corresponds to a unique callback stub because each listener can subscribe to
+ // different AudioAttributes.
+ private final ConcurrentHashMap<OnDevicesForAttributesChangedListener,
+ IDevicesForAttributesCallbackStub> mDevicesForAttributesListenerToStub =
+ new ConcurrentHashMap<>();
+
+ private static final class IDevicesForAttributesCallbackStub
+ extends IDevicesForAttributesCallback.Stub {
+ ListenerInfo<OnDevicesForAttributesChangedListener> mInfo;
+
+ IDevicesForAttributesCallbackStub(@NonNull OnDevicesForAttributesChangedListener listener,
+ @NonNull Executor executor) {
+ mInfo = new ListenerInfo<>(listener, executor);
+ }
+
+ public void register(boolean register, AudioAttributes attributes) {
+ try {
+ if (register) {
+ getService().addOnDevicesForAttributesChangedListener(attributes, this);
+ } else {
+ getService().removeOnDevicesForAttributesChangedListener(this);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public void onDevicesForAttributesChanged(AudioAttributes attributes, boolean forVolume,
+ List<AudioDeviceAttributes> devices) {
+ // forVolume is ignored. The case where it is `true` is not handled.
+ mInfo.mExecutor.execute(() ->
+ mInfo.mListener.onDevicesForAttributesChanged(
+ attributes, devices));
+ }
+ }
+
+ /**
+ * @hide
+ * Interface to be notified of when routing changes for the registered audio attributes.
+ */
+ @SystemApi
+ public interface OnDevicesForAttributesChangedListener {
+ /**
+ * Called on the listener to indicate that the audio devices for the given audio
+ * attributes have changed.
+ * @param attributes the {@link AudioAttributes} whose routing changed
+ * @param devices a list of newly routed audio devices
+ */
+ void onDevicesForAttributesChanged(@NonNull AudioAttributes attributes,
+ @NonNull List<AudioDeviceAttributes> devices);
+ }
+
+ /**
+ * @hide
+ * Adds a listener for being notified of routing changes for the given {@link AudioAttributes}.
+ * @param attributes the {@link AudioAttributes} to listen for routing changes
+ * @param executor
+ * @param listener
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MODIFY_AUDIO_ROUTING,
+ android.Manifest.permission.QUERY_AUDIO_STATE
+ })
+ public void addOnDevicesForAttributesChangedListener(@NonNull AudioAttributes attributes,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnDevicesForAttributesChangedListener listener) {
+ Objects.requireNonNull(attributes);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(listener);
+
+ synchronized (mDevicesForAttributesListenerToStub) {
+ IDevicesForAttributesCallbackStub callbackStub =
+ mDevicesForAttributesListenerToStub.get(listener);
+
+ if (callbackStub == null) {
+ callbackStub = new IDevicesForAttributesCallbackStub(listener, executor);
+ mDevicesForAttributesListenerToStub.put(listener, callbackStub);
+ }
+
+ callbackStub.register(true, attributes);
+ }
+ }
+
+ /**
+ * @hide
+ * Removes a previously registered listener for being notified of routing changes for the given
+ * {@link AudioAttributes}.
+ * @param listener
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MODIFY_AUDIO_ROUTING,
+ android.Manifest.permission.QUERY_AUDIO_STATE
+ })
+ public void removeOnDevicesForAttributesChangedListener(
+ @NonNull OnDevicesForAttributesChangedListener listener) {
+ Objects.requireNonNull(listener);
+
+ synchronized (mDevicesForAttributesListenerToStub) {
+ IDevicesForAttributesCallbackStub callbackStub =
+ mDevicesForAttributesListenerToStub.get(listener);
+ if (callbackStub != null) {
+ callbackStub.register(false, null /* attributes */);
+ }
+
+ mDevicesForAttributesListenerToStub.remove(listener);
+ }
+ }
+
/**
* Get the audio devices that would be used for the routing of the given audio attributes.
* These are the devices anticipated to play sound from an {@link AudioTrack} created with
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 0de367d3bc7e..5ee32d61e1c1 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -36,6 +36,7 @@ import android.media.IAudioServerStateDispatcher;
import android.media.ICapturePresetDevicesRoleDispatcher;
import android.media.ICommunicationDeviceDispatcher;
import android.media.IDeviceVolumeBehaviorDispatcher;
+import android.media.IDevicesForAttributesCallback;
import android.media.IMuteAwaitConnectionCallback;
import android.media.IPlaybackConfigDispatcher;
import android.media.IPreferredMixerAttributesDispatcher;
@@ -356,6 +357,12 @@ interface IAudioService {
List<AudioDeviceAttributes> getDevicesForAttributesUnprotected(in AudioAttributes attributes);
+ void addOnDevicesForAttributesChangedListener(in AudioAttributes attributes,
+ in IDevicesForAttributesCallback callback);
+
+ oneway void removeOnDevicesForAttributesChangedListener(
+ in IDevicesForAttributesCallback callback);
+
int setAllowedCapturePolicy(in int capturePolicy);
int getAllowedCapturePolicy();
diff --git a/media/java/android/media/IDevicesForAttributesCallback.aidl b/media/java/android/media/IDevicesForAttributesCallback.aidl
new file mode 100644
index 000000000000..489ecf6081e2
--- /dev/null
+++ b/media/java/android/media/IDevicesForAttributesCallback.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
+
+/**
+ * AIDL for AudioService to signal updates of audio devices routing for attributes.
+ *
+ * {@hide}
+ */
+oneway interface IDevicesForAttributesCallback {
+
+ void onDevicesForAttributesChanged(in AudioAttributes attributes, boolean forVolume,
+ in List<AudioDeviceAttributes> devices);
+
+}
+
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index f6a9162cda39..aa7e4df18186 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -50,8 +50,7 @@ interface IMediaRouterService {
// MediaRouterService.java for readability.
// Methods for MediaRouter2
- boolean verifyPackageName(String clientPackageName);
- void enforceMediaContentControlPermission();
+ boolean verifyPackageExists(String clientPackageName);
List<MediaRoute2Info> getSystemRoutes();
RoutingSessionInfo getSystemSessionInfo();
diff --git a/media/java/android/media/ImageWriter.java b/media/java/android/media/ImageWriter.java
index 32a2ad3fc5d7..93259992d339 100644
--- a/media/java/android/media/ImageWriter.java
+++ b/media/java/android/media/ImageWriter.java
@@ -431,17 +431,15 @@ public class ImageWriter implements AutoCloseable {
* @see Image#close
*/
public Image dequeueInputImage() {
- synchronized (mCloseLock) {
- if (mDequeuedImages.size() >= mMaxImages) {
- throw new IllegalStateException(
- "Already dequeued max number of Images " + mMaxImages);
- }
- WriterSurfaceImage newImage = new WriterSurfaceImage(this);
- nativeDequeueInputImage(mNativeContext, newImage);
- mDequeuedImages.add(newImage);
- newImage.mIsImageValid = true;
- return newImage;
+ if (mDequeuedImages.size() >= mMaxImages) {
+ throw new IllegalStateException(
+ "Already dequeued max number of Images " + mMaxImages);
}
+ WriterSurfaceImage newImage = new WriterSurfaceImage(this);
+ nativeDequeueInputImage(mNativeContext, newImage);
+ mDequeuedImages.add(newImage);
+ newImage.mIsImageValid = true;
+ return newImage;
}
/**
@@ -500,52 +498,50 @@ public class ImageWriter implements AutoCloseable {
throw new IllegalArgumentException("image shouldn't be null");
}
- synchronized (mCloseLock) {
- boolean ownedByMe = isImageOwnedByMe(image);
- if (ownedByMe && !(((WriterSurfaceImage) image).mIsImageValid)) {
- throw new IllegalStateException("Image from ImageWriter is invalid");
- }
-
- // For images from other components that have non-null owner, need to detach first,
- // then attach. Images without owners must already be attachable.
- if (!ownedByMe) {
- if ((image.getOwner() instanceof ImageReader)) {
- ImageReader prevOwner = (ImageReader) image.getOwner();
+ boolean ownedByMe = isImageOwnedByMe(image);
+ if (ownedByMe && !(((WriterSurfaceImage) image).mIsImageValid)) {
+ throw new IllegalStateException("Image from ImageWriter is invalid");
+ }
- prevOwner.detachImage(image);
- } else if (image.getOwner() != null) {
- throw new IllegalArgumentException(
- "Only images from ImageReader can be queued to"
- + " ImageWriter, other image source is not supported yet!");
- }
+ // For images from other components that have non-null owner, need to detach first,
+ // then attach. Images without owners must already be attachable.
+ if (!ownedByMe) {
+ if ((image.getOwner() instanceof ImageReader)) {
+ ImageReader prevOwner = (ImageReader) image.getOwner();
- attachAndQueueInputImage(image);
- // This clears the native reference held by the original owner.
- // When this Image is detached later by this ImageWriter, the
- // native memory won't be leaked.
- image.close();
- return;
+ prevOwner.detachImage(image);
+ } else if (image.getOwner() != null) {
+ throw new IllegalArgumentException(
+ "Only images from ImageReader can be queued to"
+ + " ImageWriter, other image source is not supported yet!");
}
- Rect crop = image.getCropRect();
- nativeQueueInputImage(mNativeContext, image, image.getTimestamp(), image.getDataSpace(),
- crop.left, crop.top, crop.right, crop.bottom, image.getTransform(),
- image.getScalingMode());
+ attachAndQueueInputImage(image);
+ // This clears the native reference held by the original owner.
+ // When this Image is detached later by this ImageWriter, the
+ // native memory won't be leaked.
+ image.close();
+ return;
+ }
- /**
- * Only remove and cleanup the Images that are owned by this
- * ImageWriter. Images detached from other owners are only temporarily
- * owned by this ImageWriter and will be detached immediately after they
- * are released by downstream consumers, so there is no need to keep
- * track of them in mDequeuedImages.
- */
- if (ownedByMe) {
- mDequeuedImages.remove(image);
- // Do not call close here, as close is essentially cancel image.
- WriterSurfaceImage wi = (WriterSurfaceImage) image;
- wi.clearSurfacePlanes();
- wi.mIsImageValid = false;
- }
+ Rect crop = image.getCropRect();
+ nativeQueueInputImage(mNativeContext, image, image.getTimestamp(), image.getDataSpace(),
+ crop.left, crop.top, crop.right, crop.bottom, image.getTransform(),
+ image.getScalingMode());
+
+ /**
+ * Only remove and cleanup the Images that are owned by this
+ * ImageWriter. Images detached from other owners are only temporarily
+ * owned by this ImageWriter and will be detached immediately after they
+ * are released by downstream consumers, so there is no need to keep
+ * track of them in mDequeuedImages.
+ */
+ if (ownedByMe) {
+ mDequeuedImages.remove(image);
+ // Do not call close here, as close is essentially cancel image.
+ WriterSurfaceImage wi = (WriterSurfaceImage) image;
+ wi.clearSurfacePlanes();
+ wi.mIsImageValid = false;
}
}
@@ -681,11 +677,11 @@ public class ImageWriter implements AutoCloseable {
*/
@Override
public void close() {
+ setOnImageReleasedListener(null, null);
synchronized (mCloseLock) {
if (!mIsWriterValid) {
return;
}
- setOnImageReleasedListener(null, null);
for (Image image : mDequeuedImages) {
image.close();
}
@@ -817,14 +813,12 @@ public class ImageWriter implements AutoCloseable {
}
final Handler handler;
- final boolean isWriterValid;
synchronized (iw.mListenerLock) {
handler = iw.mListenerHandler;
}
- synchronized (iw.mCloseLock) {
- isWriterValid = iw.mIsWriterValid;
- }
- if (handler != null && isWriterValid) {
+
+ if (handler != null) {
+ // The ListenerHandler will take care of ensuring that the parent ImageWriter is valid
handler.sendEmptyMessage(0);
}
}
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 5faa794151b1..fa74a9f12d7c 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -208,9 +208,9 @@ public final class MediaRouter2 {
IMediaRouterService.Stub.asInterface(
ServiceManager.getService(Context.MEDIA_ROUTER_SERVICE));
try {
- // SecurityException will be thrown if there's no permission.
- serviceBinder.enforceMediaContentControlPermission();
- if (!serviceBinder.verifyPackageName(clientPackageName)) {
+ // verifyPackageExists throws SecurityException if the caller doesn't hold
+ // MEDIA_CONTENT_CONTROL permission.
+ if (!serviceBinder.verifyPackageExists(clientPackageName)) {
Log.e(TAG, "Package " + clientPackageName + " not found. Ignoring.");
return null;
}
diff --git a/native/android/input.cpp b/native/android/input.cpp
index 812db0f1c507..5e5ebed78e61 100644
--- a/native/android/input.cpp
+++ b/native/android/input.cpp
@@ -297,6 +297,8 @@ int32_t AMotionEvent_getClassification(const AInputEvent* motion_event) {
return AMOTION_EVENT_CLASSIFICATION_DEEP_PRESS;
case android::MotionClassification::TWO_FINGER_SWIPE:
return AMOTION_EVENT_CLASSIFICATION_TWO_FINGER_SWIPE;
+ case android::MotionClassification::MULTI_FINGER_SWIPE:
+ return AMOTION_EVENT_CLASSIFICATION_MULTI_FINGER_SWIPE;
}
}
diff --git a/packages/DynamicSystemInstallationService/AndroidManifest.xml b/packages/DynamicSystemInstallationService/AndroidManifest.xml
index 176534829222..b194738c67b6 100644
--- a/packages/DynamicSystemInstallationService/AndroidManifest.xml
+++ b/packages/DynamicSystemInstallationService/AndroidManifest.xml
@@ -3,6 +3,7 @@
android:sharedUserId="android.uid.system">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MANAGE_DYNAMIC_SYSTEM" />
<uses-permission android:name="android.permission.REBOOT" />
@@ -19,6 +20,7 @@
android:enabled="true"
android:exported="true"
android:permission="android.permission.INSTALL_DYNAMIC_SYSTEM"
+ android:foregroundServiceType="systemExempted"
android:process=":dynsystem">
<intent-filter>
<action android:name="android.os.image.action.NOTIFY_IF_IN_USE" />
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index 696ea4a2c164..9e249c4c7974 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -18,6 +18,8 @@
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" />
<uses-permission android:name="com.google.android.permission.INSTALL_WEARABLE_PACKAGES" />
@@ -140,6 +142,7 @@
<!-- Wearable Components -->
<service android:name=".wear.WearPackageInstallerService"
android:permission="com.google.android.permission.INSTALL_WEARABLE_PACKAGES"
+ android:foregroundServiceType="systemExempted"
android:exported="true"/>
<provider android:name=".wear.WearPackageIconProvider"
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/AndroidManifest.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/AndroidManifest.xml
index 244b367423a4..51fc7ed64660 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/AndroidManifest.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/AndroidManifest.xml
@@ -18,6 +18,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.settingslib.widget">
- <uses-sdk android:minSdkVersion="29" />
+ <uses-sdk android:minSdkVersion="21" />
</manifest>
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/collapsing_toolbar_base_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/collapsing_toolbar_base_layout.xml
index c799b9962828..02f69f679a46 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/collapsing_toolbar_base_layout.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/collapsing_toolbar_base_layout.xml
@@ -29,6 +29,12 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?android:attr/actionBarTheme" />
+ <androidx.appcompat.widget.Toolbar
+ android:id="@+id/support_action_bar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:theme="?android:attr/actionBarTheme"
+ android:visibility="gone" />
<FrameLayout
android:id="@+id/content_frame"
android:layout_width="match_parent"
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarAppCompatActivity.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarAppCompatActivity.java
new file mode 100644
index 000000000000..dcc6e5a37246
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarAppCompatActivity.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.collapsingtoolbar;
+
+import android.app.ActionBar;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Toolbar;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.android.settingslib.utils.BuildCompatUtils;
+import com.android.settingslib.widget.R;
+
+import com.google.android.material.appbar.AppBarLayout;
+import com.google.android.material.appbar.CollapsingToolbarLayout;
+import com.google.android.material.color.DynamicColors;
+
+/**
+ * A base Activity that has a collapsing toolbar layout is used for the activities intending to
+ * enable the collapsing toolbar function.
+ */
+public class CollapsingToolbarAppCompatActivity extends AppCompatActivity {
+
+ private class DelegateCallback implements CollapsingToolbarDelegate.HostCallback {
+ @Nullable
+ @Override
+ public ActionBar setActionBar(Toolbar toolbar) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public androidx.appcompat.app.ActionBar setActionBar(
+ androidx.appcompat.widget.Toolbar toolbar) {
+ CollapsingToolbarAppCompatActivity.super.setSupportActionBar(toolbar);
+ return CollapsingToolbarAppCompatActivity.super.getSupportActionBar();
+ }
+
+ @Override
+ public void setOuterTitle(CharSequence title) {
+ CollapsingToolbarAppCompatActivity.super.setTitle(title);
+ }
+ }
+
+ private CollapsingToolbarDelegate mToolbardelegate;
+
+ private int mCustomizeLayoutResId = 0;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (BuildCompatUtils.isAtLeastS()) {
+ DynamicColors.applyToActivityIfAvailable(this);
+ }
+ setTheme(R.style.Theme_SubSettingsBase);
+
+ if (mCustomizeLayoutResId > 0 && !BuildCompatUtils.isAtLeastS()) {
+ super.setContentView(mCustomizeLayoutResId);
+ return;
+ }
+
+ View view = getToolbarDelegate().onCreateView(getLayoutInflater(), null, this);
+ super.setContentView(view);
+ }
+
+ @Override
+ public void setContentView(int layoutResID) {
+ final ViewGroup parent = (mToolbardelegate == null) ? findViewById(R.id.content_frame)
+ : mToolbardelegate.getContentFrameLayout();
+ if (parent != null) {
+ parent.removeAllViews();
+ }
+ LayoutInflater.from(this).inflate(layoutResID, parent);
+ }
+
+ @Override
+ public void setContentView(View view) {
+ final ViewGroup parent = (mToolbardelegate == null) ? findViewById(R.id.content_frame)
+ : mToolbardelegate.getContentFrameLayout();
+ if (parent != null) {
+ parent.addView(view);
+ }
+ }
+
+ @Override
+ public void setContentView(View view, ViewGroup.LayoutParams params) {
+ final ViewGroup parent = (mToolbardelegate == null) ? findViewById(R.id.content_frame)
+ : mToolbardelegate.getContentFrameLayout();
+ if (parent != null) {
+ parent.addView(view, params);
+ }
+ }
+
+ /**
+ * This method allows an activity to replace the default layout with a customize layout. Notice
+ * that it will no longer apply the features being provided by this class when this method
+ * gets called.
+ */
+ protected void setCustomizeContentView(int layoutResId) {
+ mCustomizeLayoutResId = layoutResId;
+ }
+
+ @Override
+ public void setTitle(CharSequence title) {
+ getToolbarDelegate().setTitle(title);
+ }
+
+ @Override
+ public void setTitle(int titleId) {
+ setTitle(getText(titleId));
+ }
+
+ @Override
+ public boolean onSupportNavigateUp() {
+ if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
+ getSupportFragmentManager().popBackStackImmediate();
+ }
+
+ // Closes the activity if there is no fragment inside the stack. Otherwise the activity will
+ // has a blank screen since there is no any fragment.
+ if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
+ finishAfterTransition();
+ }
+ return true;
+ }
+
+ @Override
+ public void onBackPressed() {
+ super.onBackPressed();
+
+ // Closes the activity if there is no fragment inside the stack. Otherwise the activity will
+ // has a blank screen since there is no any fragment. onBackPressed() in Activity.java only
+ // handles popBackStackImmediate(). This will close activity to avoid a blank screen.
+ if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
+ finishAfterTransition();
+ }
+ }
+
+ /**
+ * Returns an instance of collapsing toolbar.
+ */
+ @Nullable
+ public CollapsingToolbarLayout getCollapsingToolbarLayout() {
+ return getToolbarDelegate().getCollapsingToolbarLayout();
+ }
+
+ /**
+ * Return an instance of app bar.
+ */
+ @Nullable
+ public AppBarLayout getAppBarLayout() {
+ return getToolbarDelegate().getAppBarLayout();
+ }
+
+ private CollapsingToolbarDelegate getToolbarDelegate() {
+ if (mToolbardelegate == null) {
+ mToolbardelegate = new CollapsingToolbarDelegate(new DelegateCallback());
+ }
+ return mToolbardelegate;
+ }
+}
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
index 8c8b47875bec..01f92c4fa7b1 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
@@ -117,12 +117,30 @@ public class CollapsingToolbarBaseActivity extends FragmentActivity {
@Override
public boolean onNavigateUp() {
- if (!super.onNavigateUp()) {
+ if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
+ getSupportFragmentManager().popBackStackImmediate();
+ }
+
+ // Closes the activity if there is no fragment inside the stack. Otherwise the activity will
+ // has a blank screen since there is no any fragment.
+ if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
finishAfterTransition();
}
return true;
}
+ @Override
+ public void onBackPressed() {
+ super.onBackPressed();
+
+ // Closes the activity if there is no fragment inside the stack. Otherwise the activity will
+ // has a blank screen since there is no any fragment. onBackPressed() in Activity.java only
+ // handles popBackStackImmediate(). This will close activity to avoid a blank screen.
+ if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
+ finishAfterTransition();
+ }
+ }
+
/**
* Returns an instance of collapsing toolbar.
*/
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
index 01698b7937aa..1c2288acd358 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
@@ -19,9 +19,11 @@ package com.android.settingslib.collapsingtoolbar;
import static android.text.Layout.HYPHENATION_FREQUENCY_NORMAL_FAST;
import android.app.ActionBar;
+import android.app.Activity;
import android.content.res.Configuration;
import android.graphics.text.LineBreakConfig;
import android.os.Build;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -30,6 +32,7 @@ import android.widget.Toolbar;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import com.android.settingslib.widget.R;
@@ -42,7 +45,7 @@ import com.google.android.material.appbar.CollapsingToolbarLayout;
* extend from {@link CollapsingToolbarBaseActivity} or from {@link CollapsingToolbarBaseFragment}.
*/
public class CollapsingToolbarDelegate {
-
+ private static final String TAG = "CTBdelegate";
/** Interface to be implemented by the host of the Collapsing Toolbar. */
public interface HostCallback {
/**
@@ -53,6 +56,13 @@ public class CollapsingToolbarDelegate {
@Nullable
ActionBar setActionBar(Toolbar toolbar);
+ /** Sets support tool bar and return support action bar, this is for AppCompatActivity. */
+ @Nullable
+ default androidx.appcompat.app.ActionBar setActionBar(
+ androidx.appcompat.widget.Toolbar toolbar) {
+ return null;
+ }
+
/** Sets a title on the host. */
void setOuterTitle(CharSequence title);
}
@@ -79,6 +89,13 @@ public class CollapsingToolbarDelegate {
/** Method to call that creates the root view of the collapsing toolbar. */
@SuppressWarnings("RestrictTo")
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container) {
+ return onCreateView(inflater, container, null);
+ }
+
+ /** Method to call that creates the root view of the collapsing toolbar. */
+ @SuppressWarnings("RestrictTo")
+ View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ Activity activity) {
final View view =
inflater.inflate(R.layout.collapsing_toolbar_base_layout, container, false);
if (view instanceof CoordinatorLayout) {
@@ -99,17 +116,57 @@ public class CollapsingToolbarDelegate {
}
}
autoSetCollapsingToolbarLayoutScrolling();
- mToolbar = view.findViewById(R.id.action_bar);
mContentFrameLayout = view.findViewById(R.id.content_frame);
- final ActionBar actionBar = mHostCallback.setActionBar(mToolbar);
+ if (activity instanceof AppCompatActivity) {
+ Log.d(TAG, "onCreateView: from AppCompatActivity and sub-class.");
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ initSupportActionBar(inflater);
+ } else {
+ initRSupportActionBar(view);
+ }
+ } else {
+ Log.d(TAG, "onCreateView: from NonAppCompatActivity.");
+ mToolbar = view.findViewById(R.id.action_bar);
+ final ActionBar actionBar = mHostCallback.setActionBar(mToolbar);
+ // Enable title and home button by default
+ if (actionBar != null) {
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.setHomeButtonEnabled(true);
+ actionBar.setDisplayShowTitleEnabled(true);
+ }
+ }
+ return view;
+ }
- // Enable title and home button by default
+ private void initSupportActionBar(@NonNull LayoutInflater inflater) {
+ if (mCollapsingToolbarLayout == null) {
+ return;
+ }
+ mCollapsingToolbarLayout.removeAllViews();
+ inflater.inflate(R.layout.support_toolbar, mCollapsingToolbarLayout);
+ final androidx.appcompat.widget.Toolbar supportToolbar =
+ mCollapsingToolbarLayout.findViewById(R.id.support_action_bar);
+ final androidx.appcompat.app.ActionBar actionBar =
+ mHostCallback.setActionBar(supportToolbar);
+ if (actionBar != null) {
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.setHomeButtonEnabled(true);
+ actionBar.setDisplayShowTitleEnabled(true);
+ }
+ }
+
+ private void initRSupportActionBar(View view) {
+ view.findViewById(R.id.action_bar).setVisibility(View.GONE);
+ final androidx.appcompat.widget.Toolbar supportToolbar =
+ view.findViewById(R.id.support_action_bar);
+ supportToolbar.setVisibility(View.VISIBLE);
+ final androidx.appcompat.app.ActionBar actionBar =
+ mHostCallback.setActionBar(supportToolbar);
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeButtonEnabled(true);
actionBar.setDisplayShowTitleEnabled(true);
}
- return view;
}
/** Return an instance of CoordinatorLayout. */
@@ -160,9 +217,13 @@ public class CollapsingToolbarDelegate {
new AppBarLayout.Behavior.DragCallback() {
@Override
public boolean canDrag(@NonNull AppBarLayout appBarLayout) {
- // Header can be scrolling while device in landscape mode.
- return appBarLayout.getResources().getConfiguration().orientation
- == Configuration.ORIENTATION_LANDSCAPE;
+ // Header can be scrolling while device in landscape mode and SDK > 33
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.TIRAMISU) {
+ return false;
+ } else {
+ return appBarLayout.getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_LANDSCAPE;
+ }
}
});
params.setBehavior(behavior);
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
index d67ac3b86050..e4e34f8cae1e 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
@@ -49,7 +49,7 @@ import com.google.android.material.appbar.CollapsingToolbarLayout;
*/
@RequiresApi(Build.VERSION_CODES.S)
public class CollapsingCoordinatorLayout extends CoordinatorLayout {
- private static final String TAG = "CollapsingCoordinatorLayout";
+ private static final String TAG = "CollapsingCoordinator";
private static final float TOOLBAR_LINE_SPACING_MULTIPLIER = 1.1f;
private CharSequence mToolbarTitle;
@@ -255,9 +255,13 @@ public class CollapsingCoordinatorLayout extends CoordinatorLayout {
new AppBarLayout.Behavior.DragCallback() {
@Override
public boolean canDrag(@NonNull AppBarLayout appBarLayout) {
- // Header can be scrolling while device in landscape mode.
- return appBarLayout.getResources().getConfiguration().orientation
- == Configuration.ORIENTATION_LANDSCAPE;
+ // Header can be scrolling while device in landscape mode and SDK > 33
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.TIRAMISU) {
+ return false;
+ } else {
+ return appBarLayout.getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_LANDSCAPE;
+ }
}
});
params.setBehavior(behavior);
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml
index 59ae1221ddd3..b39d09f38b62 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml
@@ -18,7 +18,11 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
- android:layout_width="match_parent">
+ android:layout_width="match_parent"
+ android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingRight="?android:attr/listPreferredItemPaddingRight"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
<TextView
android:id="@+id/switch_text"
@@ -50,7 +54,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
- android:layout_marginEnd="@dimen/settingslib_switchbar_subsettings_margin_end"
android:focusable="false"
android:clickable="false"
android:theme="@style/SwitchBar.Switch.Settingslib"/>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values-sw600dp/dmiens.xml b/packages/SettingsLib/MainSwitchPreference/res/values-sw600dp/dmiens.xml
deleted file mode 100644
index 55a2589102d3..000000000000
--- a/packages/SettingsLib/MainSwitchPreference/res/values-sw600dp/dmiens.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2021 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- -->
-
-<resources>
-
- <!-- SwitchBar sub settings margin start / end -->
- <dimen name="settingslib_switchbar_subsettings_margin_start">80dp</dimen>
-</resources>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values-sw720dp-land/dmiens.xml b/packages/SettingsLib/MainSwitchPreference/res/values-sw720dp-land/dmiens.xml
deleted file mode 100644
index 53995bcf055b..000000000000
--- a/packages/SettingsLib/MainSwitchPreference/res/values-sw720dp-land/dmiens.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2021 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- -->
-
-<resources>
-
- <!-- SwitchBar sub settings margin start / end -->
- <dimen name="settingslib_switchbar_subsettings_margin_start">128dp</dimen>
- <dimen name="settingslib_switchbar_subsettings_margin_end">128dp</dimen>
-</resources>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values-sw720dp/dmiens.xml b/packages/SettingsLib/MainSwitchPreference/res/values-sw720dp/dmiens.xml
deleted file mode 100644
index 9015c581eff5..000000000000
--- a/packages/SettingsLib/MainSwitchPreference/res/values-sw720dp/dmiens.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2021 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- -->
-
-<resources>
-
- <!-- SwitchBar sub settings margin start / end -->
- <dimen name="settingslib_switchbar_subsettings_margin_start">80dp</dimen>
- <dimen name="settingslib_switchbar_subsettings_margin_end">80dp</dimen>
-</resources>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml b/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml
index 157a54e3573d..88b2c8728495 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml
@@ -27,6 +27,6 @@
<dimen name="settingslib_switch_title_margin">24dp</dimen>
<!-- SwitchBar sub settings margin start / end -->
- <dimen name="settingslib_switchbar_subsettings_margin_start">72dp</dimen>
+ <dimen name="settingslib_switchbar_subsettings_margin_start">56dp</dimen>
<dimen name="settingslib_switchbar_subsettings_margin_end">16dp</dimen>
</resources>
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
index 86fec50d7c21..864a8bb17058 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
@@ -161,6 +161,19 @@ public class MainSwitchBar extends LinearLayout implements CompoundButton.OnChec
}
/**
+ * Set icon space reserved for title
+ */
+ public void setIconSpaceReserved(boolean iconSpaceReserved) {
+ if (mTextView != null && !BuildCompatUtils.isAtLeastS()) {
+ LayoutParams params = (LayoutParams) mTextView.getLayoutParams();
+ int iconSpace = getContext().getResources().getDimensionPixelSize(
+ R.dimen.settingslib_switchbar_subsettings_margin_start);
+ params.setMarginStart(iconSpaceReserved ? iconSpace : 0);
+ mTextView.setLayoutParams(params);
+ }
+ }
+
+ /**
* Show the MainSwitchBar
*/
public void show() {
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
index fc0e05f7fb46..53cc268851e7 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
@@ -37,7 +37,6 @@ public class MainSwitchPreference extends TwoStatePreference implements OnMainSw
private final List<OnMainSwitchChangeListener> mSwitchChangeListeners = new ArrayList<>();
private MainSwitchBar mMainSwitchBar;
- private CharSequence mTitle;
public MainSwitchPreference(Context context) {
super(context);
@@ -68,6 +67,10 @@ public class MainSwitchPreference extends TwoStatePreference implements OnMainSw
holder.setDividerAllowedBelow(false);
mMainSwitchBar = (MainSwitchBar) holder.findViewById(R.id.settingslib_main_switch_bar);
+ // To support onPreferenceChange callback, it needs to call callChangeListener() when
+ // MainSwitchBar is clicked.
+ mMainSwitchBar.setOnClickListener((view) -> callChangeListener(isChecked()));
+ setIconSpaceReserved(isIconSpaceReserved());
updateStatus(isChecked());
registerListenerToSwitchBar();
}
@@ -82,6 +85,10 @@ public class MainSwitchPreference extends TwoStatePreference implements OnMainSw
final CharSequence title = a.getText(
androidx.preference.R.styleable.Preference_android_title);
setTitle(title);
+
+ final boolean bIconSpaceReserved = a.getBoolean(
+ androidx.preference.R.styleable.Preference_android_iconSpaceReserved, true);
+ setIconSpaceReserved(bIconSpaceReserved);
a.recycle();
}
}
@@ -96,9 +103,17 @@ public class MainSwitchPreference extends TwoStatePreference implements OnMainSw
@Override
public void setTitle(CharSequence title) {
- mTitle = title;
+ super.setTitle(title);
if (mMainSwitchBar != null) {
- mMainSwitchBar.setTitle(mTitle);
+ mMainSwitchBar.setTitle(title);
+ }
+ }
+
+ @Override
+ public void setIconSpaceReserved(boolean iconSpaceReserved) {
+ super.setIconSpaceReserved(iconSpaceReserved);
+ if (mMainSwitchBar != null) {
+ mMainSwitchBar.setIconSpaceReserved(iconSpaceReserved);
}
}
@@ -113,7 +128,7 @@ public class MainSwitchPreference extends TwoStatePreference implements OnMainSw
public void updateStatus(boolean checked) {
setChecked(checked);
if (mMainSwitchBar != null) {
- mMainSwitchBar.setTitle(mTitle);
+ mMainSwitchBar.setTitle(getTitle());
mMainSwitchBar.show();
}
}
@@ -125,6 +140,7 @@ public class MainSwitchPreference extends TwoStatePreference implements OnMainSw
if (!mSwitchChangeListeners.contains(listener)) {
mSwitchChangeListeners.add(listener);
}
+
if (mMainSwitchBar != null) {
mMainSwitchBar.addOnSwitchChangeListener(listener);
}
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml
index bda478e6e4fe..f1e028b405db 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml
@@ -17,6 +17,7 @@
<resources>
<style name="PreferenceTheme.SettingsLib" parent="@style/PreferenceThemeOverlay">
<item name="preferenceCategoryTitleTextAppearance">@style/TextAppearance.CategoryTitle.SettingsLib</item>
+ <item name="preferenceScreenStyle">@style/SettingsPreferenceScreen.SettingsLib</item>
<item name="preferenceCategoryStyle">@style/SettingsCategoryPreference.SettingsLib</item>
<item name="preferenceStyle">@style/SettingsPreference.SettingsLib</item>
<item name="checkBoxPreferenceStyle">@style/SettingsCheckBoxPreference.SettingsLib</item>
@@ -28,6 +29,11 @@
<item name="footerPreferenceStyle">@style/Preference.Material</item>
</style>
+ <style name="SettingsPreferenceScreen.SettingsLib" parent="@style/Preference.PreferenceScreen.Material">
+ <item name="layout">@layout/settingslib_preference</item>
+ <item name="iconSpaceReserved">@bool/settingslib_config_icon_space_reserved</item>
+ </style>
+
<style name="SettingsCategoryPreference.SettingsLib" parent="@style/Preference.Category.Material">
<item name="iconSpaceReserved">@bool/settingslib_config_icon_space_reserved</item>
<item name="allowDividerAbove">@bool/settingslib_config_allow_divider</item>
diff --git a/packages/SettingsLib/SettingsTheme/res/values/styles.xml b/packages/SettingsLib/SettingsTheme/res/values/styles.xml
index 328ab46ed2f9..af3fc4817c45 100644
--- a/packages/SettingsLib/SettingsTheme/res/values/styles.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values/styles.xml
@@ -28,19 +28,19 @@
</style>
<style name="TextAppearance.TopIntroText"
- parent="@android:style/TextAppearance.DeviceDefault">
+ parent="@android:style/TextAppearance.DeviceDefault">
<item name="android:textSize">14sp</item>
<item name="android:textColor">?android:attr/textColorSecondary</item>
</style>
<style name="TextAppearance.EntityHeaderTitle"
- parent="@android:style/TextAppearance.DeviceDefault.WindowTitle">
+ parent="@android:style/TextAppearance.DeviceDefault.WindowTitle">
<item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:textSize">20sp</item>
</style>
<style name="TextAppearance.EntityHeaderSummary"
- parent="@android:style/TextAppearance.DeviceDefault">
+ parent="@android:style/TextAppearance.DeviceDefault">
<item name="android:textAlignment">viewStart</item>
<item name="android:textColor">?android:attr/textColorSecondary</item>
<item name="android:singleLine">true</item>
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt
index ba769d206fd5..8fdc22f7bb3c 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt
@@ -19,9 +19,11 @@ package com.android.settingslib.spa.gallery.ui
import android.os.Bundle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPage
@@ -32,6 +34,7 @@ import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
import com.android.settingslib.spa.widget.ui.Spinner
+import com.android.settingslib.spa.widget.ui.SpinnerOption
private const val TITLE = "Sample Spinner"
@@ -55,16 +58,16 @@ object SpinnerPageProvider : SettingsPageProvider {
@Composable
override fun Page(arguments: Bundle?) {
RegularScaffold(title = getTitle(arguments)) {
- val selectedIndex = rememberSaveable { mutableStateOf(0) }
+ var selectedId by rememberSaveable { mutableStateOf(1) }
Spinner(
- options = (1..3).map { "Option $it" },
- selectedIndex = selectedIndex.value,
- setIndex = { selectedIndex.value = it },
+ options = (1..3).map { SpinnerOption(id = it, text = "Option $it") },
+ selectedId = selectedId,
+ setId = { selectedId = it },
)
Preference(object : PreferenceModel {
- override val title = "Selected index"
+ override val title = "Selected id"
override val summary = remember {
- derivedStateOf { selectedIndex.value.toString() }
+ derivedStateOf { selectedId.toString() }
}
})
}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/SpinnerScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/SpinnerScreenshotTest.kt
index e9b5b306fc0a..0b4d5e4ad629 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/SpinnerScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/SpinnerScreenshotTest.kt
@@ -17,6 +17,7 @@
package com.android.settingslib.spa.screenshot
import com.android.settingslib.spa.widget.ui.Spinner
+import com.android.settingslib.spa.widget.ui.SpinnerOption
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -43,9 +44,9 @@ class SpinnerScreenshotTest(emulationSpec: DeviceEmulationSpec) {
fun test() {
screenshotRule.screenshotTest("spinner") {
Spinner(
- options = (1..3).map { "Option $it" },
- selectedIndex = 0,
- setIndex = {},
+ options = (1..3).map { SpinnerOption(id = it, text = "Option $it") },
+ selectedId = 1,
+ setId = {},
)
}
}
diff --git a/packages/SettingsLib/Spa/spa/Android.bp b/packages/SettingsLib/Spa/spa/Android.bp
index 40613ceaaf5f..139f3e13d5bc 100644
--- a/packages/SettingsLib/Spa/spa/Android.bp
+++ b/packages/SettingsLib/Spa/spa/Android.bp
@@ -27,6 +27,7 @@ android_library {
"androidx.slice_slice-builders",
"androidx.slice_slice-core",
"androidx.slice_slice-view",
+ "androidx.compose.animation_animation",
"androidx.compose.material3_material3",
"androidx.compose.material_material-icons-extended",
"androidx.compose.runtime_runtime",
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index aa10cc82a14e..a81e2e330b0f 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalAnimationApi::class)
+
package com.android.settingslib.spa.framework
import android.content.Intent
@@ -21,25 +23,31 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.annotation.VisibleForTesting
+import androidx.compose.animation.AnimatedContentScope
+import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.unit.IntOffset
import androidx.core.view.WindowCompat
import androidx.navigation.NavGraph.Companion.findStartDestination
-import androidx.navigation.compose.NavHost
-import androidx.navigation.compose.composable
-import androidx.navigation.compose.rememberNavController
import com.android.settingslib.spa.R
import com.android.settingslib.spa.framework.common.LogCategory
import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.compose.AnimatedNavHost
import com.android.settingslib.spa.framework.compose.LocalNavController
import com.android.settingslib.spa.framework.compose.NavControllerWrapperImpl
+import com.android.settingslib.spa.framework.compose.composable
import com.android.settingslib.spa.framework.compose.localNavController
+import com.android.settingslib.spa.framework.compose.rememberAnimatedNavController
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.framework.util.PageEvent
import com.android.settingslib.spa.framework.util.getDestination
@@ -86,7 +94,7 @@ open class BrowseActivity : ComponentActivity() {
@VisibleForTesting
@Composable
fun BrowseContent(sppRepository: SettingsPageProviderRepository, initialIntent: Intent? = null) {
- val navController = rememberNavController()
+ val navController = rememberAnimatedNavController()
CompositionLocalProvider(navController.localNavController()) {
val controller = LocalNavController.current as NavControllerWrapperImpl
controller.NavContent(sppRepository.getAllProviders())
@@ -97,15 +105,41 @@ fun BrowseContent(sppRepository: SettingsPageProviderRepository, initialIntent:
@Composable
private fun NavControllerWrapperImpl.NavContent(allProvider: Collection<SettingsPageProvider>) {
val nullPage = SettingsPage.createNull()
- NavHost(
+ AnimatedNavHost(
navController = navController,
startDestination = nullPage.sppName,
) {
+ val slideEffect = tween<IntOffset>(durationMillis = 300)
+ val fadeEffect = tween<Float>(durationMillis = 300)
composable(nullPage.sppName) {}
for (spp in allProvider) {
composable(
route = spp.name + spp.parameter.navRoute(),
arguments = spp.parameter,
+ enterTransition = {
+ slideIntoContainer(
+ AnimatedContentScope.SlideDirection.Left,
+ animationSpec = slideEffect
+ ) + fadeIn(animationSpec = fadeEffect)
+ },
+ exitTransition = {
+ slideOutOfContainer(
+ AnimatedContentScope.SlideDirection.Left,
+ animationSpec = slideEffect
+ ) + fadeOut(animationSpec = fadeEffect)
+ },
+ popEnterTransition = {
+ slideIntoContainer(
+ AnimatedContentScope.SlideDirection.Right,
+ animationSpec = slideEffect
+ ) + fadeIn(animationSpec = fadeEffect)
+ },
+ popExitTransition = {
+ slideOutOfContainer(
+ AnimatedContentScope.SlideDirection.Right,
+ animationSpec = slideEffect
+ ) + fadeOut(animationSpec = fadeEffect)
+ },
) { navBackStackEntry ->
spp.PageEvent(navBackStackEntry.arguments)
spp.Page(navBackStackEntry.arguments)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedComposeNavigator.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedComposeNavigator.kt
new file mode 100644
index 000000000000..930a83f76e3f
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedComposeNavigator.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+package com.android.settingslib.spa.framework.compose
+
+import androidx.compose.animation.AnimatedVisibilityScope
+import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.navigation.NavBackStackEntry
+import androidx.navigation.NavDestination
+import androidx.navigation.NavOptions
+import androidx.navigation.Navigator
+
+/**
+ * Navigator that navigates through [Composable]s. Every destination using this Navigator must
+ * set a valid [Composable] by setting it directly on an instantiated [Destination] or calling
+ * [composable].
+ */
+@ExperimentalAnimationApi
+@Navigator.Name("animatedComposable")
+public class AnimatedComposeNavigator : Navigator<AnimatedComposeNavigator.Destination>() {
+ internal val transitionsInProgress get() = state.transitionsInProgress
+
+ internal val backStack get() = state.backStack
+
+ internal val isPop = mutableStateOf(false)
+
+ override fun navigate(
+ entries: List<NavBackStackEntry>,
+ navOptions: NavOptions?,
+ navigatorExtras: Extras?
+ ) {
+ entries.forEach { entry ->
+ state.pushWithTransition(entry)
+ }
+ isPop.value = false
+ }
+
+ override fun createDestination(): Destination {
+ return Destination(this, content = { })
+ }
+
+ override fun popBackStack(popUpTo: NavBackStackEntry, savedState: Boolean) {
+ state.popWithTransition(popUpTo, savedState)
+ isPop.value = true
+ }
+
+ internal fun markTransitionComplete(entry: NavBackStackEntry) {
+ state.markTransitionComplete(entry)
+ }
+
+ /**
+ * NavDestination specific to [AnimatedComposeNavigator]
+ */
+ @ExperimentalAnimationApi
+ @NavDestination.ClassType(Composable::class)
+ public class Destination(
+ navigator: AnimatedComposeNavigator,
+ internal val content: @Composable AnimatedVisibilityScope.(NavBackStackEntry) -> Unit
+ ) : NavDestination(navigator)
+
+ internal companion object {
+ internal const val NAME = "animatedComposable"
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavHost.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavHost.kt
new file mode 100644
index 000000000000..013757282427
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavHost.kt
@@ -0,0 +1,267 @@
+/*
+ * 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.
+ */
+
+package com.android.settingslib.spa.framework.compose
+
+import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.AnimatedContentScope
+import androidx.compose.animation.ContentTransform
+import androidx.compose.animation.EnterTransition
+import androidx.compose.animation.ExitTransition
+import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.core.updateTransition
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.with
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveableStateHolder
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
+import androidx.navigation.NavBackStackEntry
+import androidx.navigation.NavDestination
+import androidx.navigation.NavDestination.Companion.hierarchy
+import androidx.navigation.NavGraph
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.NavHostController
+import androidx.navigation.Navigator
+import androidx.navigation.compose.DialogHost
+import androidx.navigation.compose.DialogNavigator
+import androidx.navigation.compose.LocalOwnersProvider
+import androidx.navigation.createGraph
+import androidx.navigation.get
+import kotlinx.coroutines.flow.map
+
+/**
+ * Provides in place in the Compose hierarchy for self contained navigation to occur.
+ *
+ * Once this is called, any Composable within the given [NavGraphBuilder] can be navigated to from
+ * the provided [navController].
+ *
+ * The builder passed into this method is [remember]ed. This means that for this NavHost, the
+ * contents of the builder cannot be changed.
+ *
+ * @param navController the navController for this host
+ * @param startDestination the route for the start destination
+ * @param modifier The modifier to be applied to the layout.
+ * @param route the route for the graph
+ * @param enterTransition callback to define enter transitions for destination in this host
+ * @param exitTransition callback to define exit transitions for destination in this host
+ * @param popEnterTransition callback to define popEnter transitions for destination in this host
+ * @param popExitTransition callback to define popExit transitions for destination in this host
+ * @param builder the builder used to construct the graph
+ */
+@Composable
+@ExperimentalAnimationApi
+public fun AnimatedNavHost(
+ navController: NavHostController,
+ startDestination: String,
+ modifier: Modifier = Modifier,
+ contentAlignment: Alignment = Alignment.Center,
+ route: String? = null,
+ enterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition) =
+ { fadeIn(animationSpec = tween(700)) },
+ exitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition) =
+ { fadeOut(animationSpec = tween(700)) },
+ popEnterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition) =
+ enterTransition,
+ popExitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition) =
+ exitTransition,
+ builder: NavGraphBuilder.() -> Unit
+) {
+ AnimatedNavHost(
+ navController,
+ remember(route, startDestination, builder) {
+ navController.createGraph(startDestination, route, builder)
+ },
+ modifier,
+ contentAlignment,
+ enterTransition,
+ exitTransition,
+ popEnterTransition,
+ popExitTransition
+ )
+}
+
+/**
+ * Provides in place in the Compose hierarchy for self contained navigation to occur.
+ *
+ * Once this is called, any Composable within the given [NavGraphBuilder] can be navigated to from
+ * the provided [navController].
+ *
+ * @param navController the navController for this host
+ * @param graph the graph for this host
+ * @param modifier The modifier to be applied to the layout.
+ * @param enterTransition callback to define enter transitions for destination in this host
+ * @param exitTransition callback to define exit transitions for destination in this host
+ * @param popEnterTransition callback to define popEnter transitions for destination in this host
+ * @param popExitTransition callback to define popExit transitions for destination in this host
+ */
+@ExperimentalAnimationApi
+@Composable
+public fun AnimatedNavHost(
+ navController: NavHostController,
+ graph: NavGraph,
+ modifier: Modifier = Modifier,
+ contentAlignment: Alignment = Alignment.Center,
+ enterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition) =
+ { fadeIn(animationSpec = tween(700)) },
+ exitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition) =
+ { fadeOut(animationSpec = tween(700)) },
+ popEnterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition) =
+ enterTransition,
+ popExitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition) =
+ exitTransition,
+) {
+
+ val lifecycleOwner = LocalLifecycleOwner.current
+ val viewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {
+ "NavHost requires a ViewModelStoreOwner to be provided via LocalViewModelStoreOwner"
+ }
+ val onBackPressedDispatcherOwner = LocalOnBackPressedDispatcherOwner.current
+ val onBackPressedDispatcher = onBackPressedDispatcherOwner?.onBackPressedDispatcher
+
+ // on successful recompose we setup the navController with proper inputs
+ // after the first time, this will only happen again if one of the inputs changes
+ navController.setLifecycleOwner(lifecycleOwner)
+ navController.setViewModelStore(viewModelStoreOwner.viewModelStore)
+ if (onBackPressedDispatcher != null) {
+ navController.setOnBackPressedDispatcher(onBackPressedDispatcher)
+ }
+
+ navController.graph = graph
+
+ val saveableStateHolder = rememberSaveableStateHolder()
+
+ // Find the ComposeNavigator, returning early if it isn't found
+ // (such as is the case when using TestNavHostController)
+ val composeNavigator = navController.navigatorProvider.get<Navigator<out NavDestination>>(
+ AnimatedComposeNavigator.NAME
+ ) as? AnimatedComposeNavigator ?: return
+ val visibleEntries by remember(navController.visibleEntries) {
+ navController.visibleEntries.map {
+ it.filter { entry ->
+ entry.destination.navigatorName == AnimatedComposeNavigator.NAME
+ }
+ }
+ }.collectAsState(emptyList())
+
+ val backStackEntry = visibleEntries.lastOrNull()
+
+ if (backStackEntry != null) {
+ val finalEnter: AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition = {
+ val targetDestination = targetState.destination as AnimatedComposeNavigator.Destination
+
+ if (composeNavigator.isPop.value) {
+ targetDestination.hierarchy.firstNotNullOfOrNull { destination ->
+ popEnterTransitions[destination.route]?.invoke(this)
+ } ?: popEnterTransition.invoke(this)
+ } else {
+ targetDestination.hierarchy.firstNotNullOfOrNull { destination ->
+ enterTransitions[destination.route]?.invoke(this)
+ } ?: enterTransition.invoke(this)
+ }
+ }
+
+ val finalExit: AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition = {
+ val initialDestination =
+ initialState.destination as AnimatedComposeNavigator.Destination
+
+ if (composeNavigator.isPop.value) {
+ initialDestination.hierarchy.firstNotNullOfOrNull { destination ->
+ popExitTransitions[destination.route]?.invoke(this)
+ } ?: popExitTransition.invoke(this)
+ } else {
+ initialDestination.hierarchy.firstNotNullOfOrNull { destination ->
+ exitTransitions[destination.route]?.invoke(this)
+ } ?: exitTransition.invoke(this)
+ }
+ }
+
+ val transition = updateTransition(backStackEntry, label = "entry")
+ transition.AnimatedContent(
+ modifier,
+ transitionSpec = {
+ val zIndex = if (composeNavigator.isPop.value) {
+ visibleEntries.indexOf(initialState).toFloat()
+ } else {
+ visibleEntries.indexOf(targetState).toFloat()
+ }
+ // If the initialState of the AnimatedContent is not in visibleEntries, we are in
+ // a case where visible has cleared the old state for some reason, so instead of
+ // attempting to animate away from the initialState, we skip the animation.
+ if (initialState in visibleEntries) {
+ ContentTransform(finalEnter(this), finalExit(this), zIndex)
+ } else {
+ EnterTransition.None with ExitTransition.None
+ }
+ },
+ contentAlignment,
+ contentKey = { it.id }
+ ) {
+ // In some specific cases, such as clearing your back stack by changing your
+ // start destination, AnimatedContent can contain an entry that is no longer
+ // part of visible entries since it was cleared from the back stack and is not
+ // animating. In these cases the currentEntry will be null, and in those cases,
+ // AnimatedContent will just skip attempting to transition the old entry.
+ // See https://issuetracker.google.com/238686802
+ val currentEntry = visibleEntries.lastOrNull { entry ->
+ it == entry
+ }
+ // while in the scope of the composable, we provide the navBackStackEntry as the
+ // ViewModelStoreOwner and LifecycleOwner
+ currentEntry?.LocalOwnersProvider(saveableStateHolder) {
+ (currentEntry.destination as AnimatedComposeNavigator.Destination)
+ .content(this, currentEntry)
+ }
+ }
+ if (transition.currentState == transition.targetState) {
+ visibleEntries.forEach { entry ->
+ composeNavigator.markTransitionComplete(entry)
+ }
+ }
+ }
+
+ val dialogNavigator = navController.navigatorProvider.get<Navigator<out NavDestination>>(
+ "dialog"
+ ) as? DialogNavigator ?: return
+
+ // Show any dialog destinations
+ DialogHost(dialogNavigator)
+}
+
+@ExperimentalAnimationApi
+internal val enterTransitions =
+ mutableMapOf<String?,
+ (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?)?>()
+
+@ExperimentalAnimationApi
+internal val exitTransitions =
+ mutableMapOf<String?, (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?)?>()
+
+@ExperimentalAnimationApi
+internal val popEnterTransitions =
+ mutableMapOf<String?, (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?)?>()
+
+@ExperimentalAnimationApi
+internal val popExitTransitions =
+ mutableMapOf<String?, (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?)?>()
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavGraphBuilder.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavGraphBuilder.kt
new file mode 100644
index 000000000000..9e58603bbaff
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavGraphBuilder.kt
@@ -0,0 +1,118 @@
+/*
+ * 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.
+ */
+
+package com.android.settingslib.spa.framework.compose
+
+import androidx.compose.animation.AnimatedContentScope
+import androidx.compose.animation.AnimatedVisibilityScope
+import androidx.compose.animation.EnterTransition
+import androidx.compose.animation.ExitTransition
+import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.runtime.Composable
+import androidx.navigation.NamedNavArgument
+import androidx.navigation.NavBackStackEntry
+import androidx.navigation.NavDeepLink
+import androidx.navigation.NavGraph
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.compose.navigation
+import androidx.navigation.get
+
+/**
+ * Add the [Composable] to the [NavGraphBuilder]
+ *
+ * @param route route for the destination
+ * @param arguments list of arguments to associate with destination
+ * @param deepLinks list of deep links to associate with the destinations
+ * @param enterTransition callback to determine the destination's enter transition
+ * @param exitTransition callback to determine the destination's exit transition
+ * @param popEnterTransition callback to determine the destination's popEnter transition
+ * @param popExitTransition callback to determine the destination's popExit transition
+ * @param content composable for the destination
+ */
+@ExperimentalAnimationApi
+public fun NavGraphBuilder.composable(
+ route: String,
+ arguments: List<NamedNavArgument> = emptyList(),
+ deepLinks: List<NavDeepLink> = emptyList(),
+ enterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?)? = null,
+ exitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?)? = null,
+ popEnterTransition: (
+ AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?
+ )? = enterTransition,
+ popExitTransition: (
+ AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?
+ )? = exitTransition,
+ content: @Composable AnimatedVisibilityScope.(NavBackStackEntry) -> Unit
+) {
+ addDestination(
+ AnimatedComposeNavigator.Destination(
+ provider[AnimatedComposeNavigator::class],
+ content
+ ).apply {
+ this.route = route
+ arguments.forEach { (argumentName, argument) ->
+ addArgument(argumentName, argument)
+ }
+ deepLinks.forEach { deepLink ->
+ addDeepLink(deepLink)
+ }
+ enterTransition?.let { enterTransitions[route] = enterTransition }
+ exitTransition?.let { exitTransitions[route] = exitTransition }
+ popEnterTransition?.let { popEnterTransitions[route] = popEnterTransition }
+ popExitTransition?.let { popExitTransitions[route] = popExitTransition }
+ }
+ )
+}
+
+/**
+ * Construct a nested [NavGraph]
+ *
+ * @param startDestination the starting destination's route for this NavGraph
+ * @param route the destination's unique route
+ * @param arguments list of arguments to associate with destination
+ * @param deepLinks list of deep links to associate with the destinations
+ * @param enterTransition callback to define enter transitions for destination in this NavGraph
+ * @param exitTransition callback to define exit transitions for destination in this NavGraph
+ * @param popEnterTransition callback to define pop enter transitions for destination in this
+ * NavGraph
+ * @param popExitTransition callback to define pop exit transitions for destination in this NavGraph
+ * @param builder the builder used to construct the graph
+ *
+ * @return the newly constructed nested NavGraph
+ */
+@ExperimentalAnimationApi
+public fun NavGraphBuilder.navigation(
+ startDestination: String,
+ route: String,
+ arguments: List<NamedNavArgument> = emptyList(),
+ deepLinks: List<NavDeepLink> = emptyList(),
+ enterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?)? = null,
+ exitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?)? = null,
+ popEnterTransition: (
+ AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?
+ )? = enterTransition,
+ popExitTransition: (
+ AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?
+ )? = exitTransition,
+ builder: NavGraphBuilder.() -> Unit
+) {
+ navigation(startDestination, route, arguments, deepLinks, builder).apply {
+ enterTransition?.let { enterTransitions[route] = enterTransition }
+ exitTransition?.let { exitTransitions[route] = exitTransition }
+ popEnterTransition?.let { popEnterTransitions[route] = popEnterTransition }
+ popExitTransition?.let { popExitTransitions[route] = popExitTransition }
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavHostController.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavHostController.kt
new file mode 100644
index 000000000000..a8ac86c2fb15
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavHostController.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+package com.android.settingslib.spa.framework.compose
+
+import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.navigation.NavDestination
+import androidx.navigation.NavHostController
+import androidx.navigation.Navigator
+import androidx.navigation.compose.rememberNavController
+
+/**
+ * Creates a NavHostController that handles the adding of the [ComposeNavigator], [DialogNavigator]
+ * and [AnimatedComposeNavigator]. Additional [androidx.navigation.Navigator] instances should be
+ * added in a [androidx.compose.runtime.SideEffect] block.
+ *
+ * @see AnimatedNavHost
+ */
+@ExperimentalAnimationApi
+@Composable
+fun rememberAnimatedNavController(
+ vararg navigators: Navigator<out NavDestination>
+): NavHostController {
+ val animatedNavigator = remember { AnimatedComposeNavigator() }
+ return rememberNavController(animatedNavigator, *navigators)
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt
index 429b81a04659..64a9c7353f07 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt
@@ -45,8 +45,13 @@ import androidx.compose.ui.unit.dp
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsTheme
+data class SpinnerOption(
+ val id: Int,
+ val text: String,
+)
+
@Composable
-fun Spinner(options: List<String>, selectedIndex: Int, setIndex: (index: Int) -> Unit) {
+fun Spinner(options: List<SpinnerOption>, selectedId: Int?, setId: (id: Int) -> Unit) {
if (options.isEmpty()) {
return
}
@@ -68,7 +73,7 @@ fun Spinner(options: List<String>, selectedIndex: Int, setIndex: (index: Int) ->
),
contentPadding = contentPadding,
) {
- SpinnerText(options[selectedIndex])
+ SpinnerText(options.find { it.id == selectedId })
Icon(
imageVector = when {
expanded -> Icons.Outlined.ArrowDropUp
@@ -83,18 +88,18 @@ fun Spinner(options: List<String>, selectedIndex: Int, setIndex: (index: Int) ->
modifier = Modifier.background(SettingsTheme.colorScheme.spinnerItemContainer),
offset = DpOffset(x = 0.dp, y = 4.dp),
) {
- options.forEachIndexed { index, option ->
+ for (option in options) {
DropdownMenuItem(
text = {
SpinnerText(
- text = option,
+ option = option,
modifier = Modifier.padding(end = 24.dp),
color = SettingsTheme.colorScheme.onSpinnerItemContainer,
)
},
onClick = {
expanded = false
- setIndex(index)
+ setId(option.id)
},
contentPadding = contentPadding,
)
@@ -105,12 +110,12 @@ fun Spinner(options: List<String>, selectedIndex: Int, setIndex: (index: Int) ->
@Composable
private fun SpinnerText(
- text: String,
+ option: SpinnerOption?,
modifier: Modifier = Modifier,
color: Color = Color.Unspecified,
) {
Text(
- text = text,
+ text = option?.text ?: "",
modifier = modifier.padding(end = SettingsDimension.itemPaddingEnd),
color = color,
style = MaterialTheme.typography.labelLarge,
@@ -121,11 +126,11 @@ private fun SpinnerText(
@Composable
private fun SpinnerPreview() {
SettingsTheme {
- var selectedIndex by rememberSaveable { mutableStateOf(0) }
+ var selectedId by rememberSaveable { mutableStateOf(1) }
Spinner(
- options = (1..3).map { "Option $it" },
- selectedIndex = selectedIndex,
- setIndex = { selectedIndex = it },
+ options = (1..3).map { SpinnerOption(id = it, text = "Option $it") },
+ selectedId = selectedId,
+ setId = { selectedId = it },
)
}
}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/SpinnerTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/SpinnerTest.kt
index 6c56d63c18c7..33a4080376fb 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/SpinnerTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/SpinnerTest.kt
@@ -36,28 +36,28 @@ class SpinnerTest {
@Test
fun spinner_initialState() {
- var selectedIndex by mutableStateOf(0)
+ var selectedId by mutableStateOf(1)
composeTestRule.setContent {
Spinner(
- options = (1..3).map { "Option $it" },
- selectedIndex = selectedIndex,
- setIndex = { selectedIndex = it },
+ options = (1..3).map { SpinnerOption(id = it, text = "Option $it") },
+ selectedId = selectedId,
+ setId = { selectedId = it },
)
}
composeTestRule.onNodeWithText("Option 1").assertIsDisplayed()
composeTestRule.onNodeWithText("Option 2").assertDoesNotExist()
- assertThat(selectedIndex).isEqualTo(0)
+ assertThat(selectedId).isEqualTo(1)
}
@Test
fun spinner_canChangeState() {
- var selectedIndex by mutableStateOf(0)
+ var selectedId by mutableStateOf(1)
composeTestRule.setContent {
Spinner(
- options = (1..3).map { "Option $it" },
- selectedIndex = selectedIndex,
- setIndex = { selectedIndex = it },
+ options = (1..3).map { SpinnerOption(id = it, text = "Option $it") },
+ selectedId = selectedId,
+ setId = { selectedId = it },
)
}
@@ -66,6 +66,6 @@ class SpinnerTest {
composeTestRule.onNodeWithText("Option 1").assertDoesNotExist()
composeTestRule.onNodeWithText("Option 2").assertIsDisplayed()
- assertThat(selectedIndex).isEqualTo(1)
+ assertThat(selectedId).isEqualTo(2)
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt
index af5c5dcd37d5..791b4e0a7f28 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt
@@ -4,6 +4,7 @@ import android.content.pm.ApplicationInfo
import android.icu.text.CollationKey
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
+import com.android.settingslib.spa.widget.ui.SpinnerOption
import com.android.settingslib.spaprivileged.template.app.AppListItem
import com.android.settingslib.spaprivileged.template.app.AppListItemModel
import kotlinx.coroutines.flow.Flow
@@ -23,7 +24,7 @@ interface AppListModel<T : AppRecord> {
*
* Default no spinner will be shown.
*/
- fun getSpinnerOptions(): List<String> = emptyList()
+ fun getSpinnerOptions(recordList: List<T>): List<SpinnerOption> = emptyList()
/**
* Loads the extra info for the App List, and generates the [AppRecord] List.
@@ -42,8 +43,10 @@ interface AppListModel<T : AppRecord> {
* This function is called when the App List's loading is finished and displayed to the user.
*
* Could do some pre-cache here.
+ *
+ * @return true to enable pre-fetching app labels.
*/
- suspend fun onFirstLoaded(recordList: List<T>) {}
+ suspend fun onFirstLoaded(recordList: List<T>) = false
/**
* Gets the comparator to sort the App List.
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
index 4c144b294fa5..cbb4fbe32713 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
@@ -21,6 +21,7 @@ import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
+import com.android.internal.R
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
@@ -49,7 +50,7 @@ internal interface AppListRepository {
}
-internal class AppListRepositoryImpl(context: Context) : AppListRepository {
+internal class AppListRepositoryImpl(private val context: Context) : AppListRepository {
private val packageManager = context.packageManager
override suspend fun loadApps(config: AppListConfig): List<ApplicationInfo> = coroutineScope {
@@ -59,6 +60,9 @@ internal class AppListRepositoryImpl(context: Context) : AppListRepository {
.map { it.packageName }
.toSet()
}
+ val hideWhenDisabledPackagesDeferred = async {
+ context.resources.getStringArray(R.array.config_hideWhenDisabled_packageNames)
+ }
val flags = PackageManager.ApplicationInfoFlags.of(
(PackageManager.MATCH_DISABLED_COMPONENTS or
PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong()
@@ -67,8 +71,9 @@ internal class AppListRepositoryImpl(context: Context) : AppListRepository {
packageManager.getInstalledApplicationsAsUser(flags, config.userId)
val hiddenSystemModules = hiddenSystemModulesDeferred.await()
+ val hideWhenDisabledPackages = hideWhenDisabledPackagesDeferred.await()
installedApplicationsAsUser.filter { app ->
- app.isInAppList(config.showInstantApps, hiddenSystemModules)
+ app.isInAppList(config.showInstantApps, hiddenSystemModules, hideWhenDisabledPackages)
}
}
@@ -116,12 +121,13 @@ internal class AppListRepositoryImpl(context: Context) : AppListRepository {
private fun ApplicationInfo.isInAppList(
showInstantApps: Boolean,
hiddenSystemModules: Set<String>,
+ hideWhenDisabledPackages: Array<String>,
) = when {
!showInstantApps && isInstantApp -> false
packageName in hiddenSystemModules -> false
+ packageName in hideWhenDisabledPackages -> enabled && !isDisabledUntilUsed
enabled -> true
- isDisabledUntilUsed -> true
- else -> false
+ else -> enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER
}
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
index 650b27845bd2..df828f2c0fa2 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
@@ -25,9 +25,11 @@ import androidx.lifecycle.viewModelScope
import com.android.settingslib.spa.framework.util.StateFlowBridge
import com.android.settingslib.spa.framework.util.asyncMapItem
import com.android.settingslib.spa.framework.util.waitFirst
+import com.android.settingslib.spa.widget.ui.SpinnerOption
import java.util.concurrent.ConcurrentHashMap
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
@@ -48,6 +50,12 @@ internal data class AppListData<T : AppRecord>(
AppListData(appEntries.filter(predicate), option)
}
+internal interface IAppListViewModel<T : AppRecord> {
+ val option: StateFlowBridge<Int?>
+ val spinnerOptionsFlow: Flow<List<SpinnerOption>>
+ val appListDataFlow: Flow<AppListData<T>>
+}
+
internal class AppListViewModel<T : AppRecord>(
application: Application,
) : AppListViewModelImpl<T>(application)
@@ -57,11 +65,11 @@ internal open class AppListViewModelImpl<T : AppRecord>(
application: Application,
appListRepositoryFactory: (Context) -> AppListRepository = ::AppListRepositoryImpl,
appRepositoryFactory: (Context) -> AppRepository = ::AppRepositoryImpl,
-) : AndroidViewModel(application) {
+) : AndroidViewModel(application), IAppListViewModel<T> {
val appListConfig = StateFlowBridge<AppListConfig>()
val listModel = StateFlowBridge<AppListModel<T>>()
val showSystem = StateFlowBridge<Boolean>()
- val option = StateFlowBridge<Int>()
+ final override val option = StateFlowBridge<Int?>()
val searchQuery = StateFlowBridge<String>()
private val appListRepository = appListRepositoryFactory(application)
@@ -84,7 +92,12 @@ internal open class AppListViewModelImpl<T : AppRecord>(
recordList.filter { showAppPredicate(it.app) }
}
- val appListDataFlow = option.flow.flatMapLatest(::filterAndSort)
+ override val spinnerOptionsFlow =
+ recordListFlow.combine(listModel.flow) { recordList, listModel ->
+ listModel.getSpinnerOptions(recordList)
+ }
+
+ override val appListDataFlow = option.flow.flatMapLatest(::filterAndSort)
.combine(searchQuery.flow) { appListData, searchQuery ->
appListData.filter {
it.label.contains(other = searchQuery, ignoreCase = true)
@@ -97,7 +110,7 @@ internal open class AppListViewModelImpl<T : AppRecord>(
}
fun reloadApps() {
- viewModelScope.launch {
+ scope.launch {
appsStateFlow.value = appListRepository.loadApps(appListConfig.flow.first())
}
}
@@ -124,17 +137,16 @@ internal open class AppListViewModelImpl<T : AppRecord>(
recordListFlow
.waitFirst(appListDataFlow)
.combine(listModel.flow) { recordList, listModel ->
- listModel.maybePreFetchLabels(recordList)
- listModel.onFirstLoaded(recordList)
+ if (listModel.onFirstLoaded(recordList)) {
+ preFetchLabels(recordList)
+ }
}
.launchIn(scope)
}
- private fun AppListModel<T>.maybePreFetchLabels(recordList: List<T>) {
- if (getSpinnerOptions().isNotEmpty()) {
- for (record in recordList) {
- getLabel(record.app)
- }
+ private fun preFetchLabels(recordList: List<T>) {
+ for (record in recordList) {
+ getLabel(record.app)
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
index e8788048776e..6cd6e951ef02 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
@@ -17,7 +17,6 @@
package com.android.settingslib.spaprivileged.model.app
import android.app.AppOpsManager.MODE_ALLOWED
-import android.app.AppOpsManager.MODE_ERRORED
import android.app.AppOpsManager.Mode
import android.content.Context
import android.content.pm.ApplicationInfo
@@ -33,14 +32,14 @@ interface IAppOpsController {
fun setAllowed(allowed: Boolean)
- @Mode
- fun getMode(): Int
+ @Mode fun getMode(): Int
}
class AppOpsController(
context: Context,
private val app: ApplicationInfo,
private val op: Int,
+ private val modeForNotAllowed: Int,
private val setModeByUid: Boolean = false,
) : IAppOpsController {
private val appOpsManager = context.appOpsManager
@@ -49,7 +48,7 @@ class AppOpsController(
get() = _mode
override fun setAllowed(allowed: Boolean) {
- val mode = if (allowed) MODE_ALLOWED else MODE_ERRORED
+ val mode = if (allowed) MODE_ALLOWED else modeForNotAllowed
if (setModeByUid) {
appOpsManager.setUidMode(op, app.uid, mode)
} else {
@@ -58,12 +57,12 @@ class AppOpsController(
_mode.postValue(mode)
}
- @Mode
- override fun getMode(): Int = appOpsManager.checkOpNoThrow(op, app.uid, app.packageName)
+ @Mode override fun getMode(): Int = appOpsManager.checkOpNoThrow(op, app.uid, app.packageName)
- private val _mode = object : MutableLiveData<Int>() {
- override fun onActive() {
- postValue(getMode())
+ private val _mode =
+ object : MutableLiveData<Int>() {
+ override fun onActive() {
+ postValue(getMode())
+ }
}
- }
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
index 3ff1d897ad6f..34c3ee0e2c0c 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
@@ -19,13 +19,16 @@ package com.android.settingslib.spaprivileged.template.app
import android.content.Intent
import android.content.IntentFilter
import android.os.UserHandle
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
@@ -34,8 +37,11 @@ import com.android.settingslib.spa.framework.compose.LogCompositions
import com.android.settingslib.spa.framework.compose.TimeMeasurer.Companion.rememberTimeMeasurer
import com.android.settingslib.spa.framework.compose.rememberLazyListStateAndHideKeyboardWhenStartScroll
import com.android.settingslib.spa.framework.compose.toState
+import com.android.settingslib.spa.framework.util.StateFlowBridge
import com.android.settingslib.spa.widget.ui.CategoryTitle
import com.android.settingslib.spa.widget.ui.PlaceholderTitle
+import com.android.settingslib.spa.widget.ui.Spinner
+import com.android.settingslib.spa.widget.ui.SpinnerOption
import com.android.settingslib.spaprivileged.R
import com.android.settingslib.spaprivileged.framework.compose.DisposableBroadcastReceiverAsUser
import com.android.settingslib.spaprivileged.model.app.AppEntry
@@ -44,6 +50,7 @@ import com.android.settingslib.spaprivileged.model.app.AppListData
import com.android.settingslib.spaprivileged.model.app.AppListModel
import com.android.settingslib.spaprivileged.model.app.AppListViewModel
import com.android.settingslib.spaprivileged.model.app.AppRecord
+import com.android.settingslib.spaprivileged.model.app.IAppListViewModel
import kotlinx.coroutines.Dispatchers
private const val TAG = "AppList"
@@ -51,7 +58,6 @@ private const val CONTENT_TYPE_HEADER = "header"
data class AppListState(
val showSystem: State<Boolean>,
- val option: State<Int>,
val searchQuery: State<String>,
)
@@ -71,16 +77,36 @@ data class AppListInput<T : AppRecord>(
*/
@Composable
fun <T : AppRecord> AppListInput<T>.AppList() {
- AppListImpl { loadAppListData(config, listModel, state) }
+ AppListImpl { rememberViewModel(config, listModel, state) }
}
@Composable
internal fun <T : AppRecord> AppListInput<T>.AppListImpl(
- appListDataSupplier: @Composable () -> State<AppListData<T>?>,
+ viewModelSupplier: @Composable () -> IAppListViewModel<T>,
) {
LogCompositions(TAG, config.userId.toString())
- val appListData = appListDataSupplier()
- listModel.AppListWidget(appListData, header, bottomPadding, noItemMessage)
+ val viewModel = viewModelSupplier()
+ Column(Modifier.fillMaxSize()) {
+ val optionsState = viewModel.spinnerOptionsFlow.collectAsState(null, Dispatchers.IO)
+ SpinnerOptions(optionsState, viewModel.option)
+ val appListData = viewModel.appListDataFlow.collectAsState(null, Dispatchers.IO)
+ listModel.AppListWidget(appListData, header, bottomPadding, noItemMessage)
+ }
+}
+
+@Composable
+private fun SpinnerOptions(
+ optionsState: State<List<SpinnerOption>?>,
+ optionBridge: StateFlowBridge<Int?>,
+) {
+ val options = optionsState.value
+ val selectedOption = rememberSaveable(options) {
+ mutableStateOf(options?.let { it.firstOrNull()?.id ?: -1 })
+ }
+ optionBridge.Sync(selectedOption)
+ if (options != null) {
+ Spinner(options, selectedOption.value) { selectedOption.value = it }
+ }
}
@Composable
@@ -131,16 +157,15 @@ private fun <T : AppRecord> AppListModel<T>.getGroupTitleIfFirst(
}
@Composable
-private fun <T : AppRecord> loadAppListData(
+private fun <T : AppRecord> rememberViewModel(
config: AppListConfig,
listModel: AppListModel<T>,
state: AppListState,
-): State<AppListData<T>?> {
+): AppListViewModel<T> {
val viewModel: AppListViewModel<T> = viewModel(key = config.userId.toString())
viewModel.appListConfig.setIfAbsent(config)
viewModel.listModel.setIfAbsent(listModel)
viewModel.showSystem.Sync(state.showSystem)
- viewModel.option.Sync(state.option)
viewModel.searchQuery.Sync(state.searchQuery)
DisposableBroadcastReceiverAsUser(
@@ -153,5 +178,5 @@ private fun <T : AppRecord> loadAppListData(
onStart = { viewModel.reloadApps() },
) { viewModel.reloadApps() }
- return viewModel.appListDataFlow.collectAsState(null, Dispatchers.IO)
+ return viewModel
} \ No newline at end of file
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
index 7d21d98820d3..404e27ccb79a 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
@@ -16,18 +16,13 @@
package com.android.settingslib.spaprivileged.template.app
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.android.settingslib.spa.widget.scaffold.MoreOptionsAction
import com.android.settingslib.spa.widget.scaffold.MoreOptionsScope
import com.android.settingslib.spa.widget.scaffold.SearchScaffold
-import com.android.settingslib.spa.widget.ui.Spinner
import com.android.settingslib.spaprivileged.R
import com.android.settingslib.spaprivileged.model.app.AppListConfig
import com.android.settingslib.spaprivileged.model.app.AppListModel
@@ -61,27 +56,21 @@ fun <T : AppRecord> AppListPage(
},
) { bottomPadding, searchQuery ->
WorkProfilePager(primaryUserOnly) { userInfo ->
- Column(Modifier.fillMaxSize()) {
- val options = remember { listModel.getSpinnerOptions() }
- val selectedOption = rememberSaveable { mutableStateOf(0) }
- Spinner(options, selectedOption.value) { selectedOption.value = it }
- val appListInput = AppListInput(
- config = AppListConfig(
- userId = userInfo.id,
- showInstantApps = showInstantApps,
- ),
- listModel = listModel,
- state = AppListState(
- showSystem = showSystem,
- option = selectedOption,
- searchQuery = searchQuery,
- ),
- header = header,
- bottomPadding = bottomPadding,
- noItemMessage = noItemMessage,
- )
- appList(appListInput)
- }
+ val appListInput = AppListInput(
+ config = AppListConfig(
+ userId = userInfo.id,
+ showInstantApps = showInstantApps,
+ ),
+ listModel = listModel,
+ state = AppListState(
+ showSystem = showSystem,
+ searchQuery = searchQuery,
+ ),
+ header = header,
+ bottomPadding = bottomPadding,
+ noItemMessage = noItemMessage,
+ )
+ appList(appListInput)
}
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
index ee21b81fe92a..53af25b81580 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
@@ -18,6 +18,7 @@ package com.android.settingslib.spaprivileged.template.app
import android.app.AppOpsManager.MODE_ALLOWED
import android.app.AppOpsManager.MODE_DEFAULT
+import android.app.AppOpsManager.MODE_ERRORED
import android.content.Context
import android.content.pm.ApplicationInfo
import androidx.compose.runtime.Composable
@@ -25,6 +26,8 @@ import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.remember
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.framework.util.asyncMapItem
import com.android.settingslib.spa.framework.util.filterItem
import com.android.settingslib.spaprivileged.model.app.AppOpsController
import com.android.settingslib.spaprivileged.model.app.AppRecord
@@ -37,6 +40,7 @@ import kotlinx.coroutines.flow.map
data class AppOpPermissionRecord(
override val app: ApplicationInfo,
+ val hasRequestBroaderPermission: Boolean,
val hasRequestPermission: Boolean,
var appOpsController: IAppOpsController,
) : AppRecord
@@ -50,9 +54,26 @@ abstract class AppOpPermissionListModel(
abstract val permission: String
/**
+ * When set, specifies the broader permission who trumps the [permission].
+ *
+ * When trumped, the [permission] is not changeable and model shows the [permission] as allowed.
+ */
+ open val broaderPermission: String? = null
+
+ /**
+ * Indicates whether [permission] has protection level appop flag.
+ *
+ * If true, it uses getAppOpPermissionPackages() to fetch bits to decide whether the permission
+ * is requested.
+ */
+ open val permissionHasAppopFlag: Boolean = true
+
+ open val modeForNotAllowed: Int = MODE_ERRORED
+
+ /**
* Use AppOpsManager#setUidMode() instead of AppOpsManager#setMode() when set allowed.
*
- * Security related app-ops should be set with setUidMode() instead of setMode().
+ * Security or privacy related app-ops should be set with setUidMode() instead of setMode().
*/
open val setModeByUid = false
@@ -60,31 +81,54 @@ abstract class AppOpPermissionListModel(
private val notChangeablePackages =
setOf("android", "com.android.systemui", context.packageName)
+ private fun createAppOpsController(app: ApplicationInfo) =
+ AppOpsController(
+ context = context,
+ app = app,
+ op = appOp,
+ setModeByUid = setModeByUid,
+ modeForNotAllowed = modeForNotAllowed,
+ )
+
+ private fun createRecord(
+ app: ApplicationInfo,
+ hasRequestPermission: Boolean
+ ): AppOpPermissionRecord =
+ with(packageManagers) {
+ AppOpPermissionRecord(
+ app = app,
+ hasRequestBroaderPermission =
+ broaderPermission?.let { app.hasRequestPermission(it) } ?: false,
+ hasRequestPermission = hasRequestPermission,
+ appOpsController = createAppOpsController(app),
+ )
+ }
+
override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
- userIdFlow.map { userId ->
- packageManagers.getAppOpPermissionPackages(userId, permission)
- }.combine(appListFlow) { packageNames, appList ->
- appList.map { app ->
- AppOpPermissionRecord(
- app = app,
- hasRequestPermission = app.packageName in packageNames,
- appOpsController = createAppOpsController(app),
- )
+ if (permissionHasAppopFlag) {
+ userIdFlow
+ .map { userId -> packageManagers.getAppOpPermissionPackages(userId, permission) }
+ .combine(appListFlow) { packageNames, appList ->
+ appList.map { app ->
+ createRecord(
+ app = app,
+ hasRequestPermission = app.packageName in packageNames,
+ )
+ }
+ }
+ } else {
+ appListFlow.asyncMapItem { app ->
+ with(packageManagers) { createRecord(app, app.hasRequestPermission(permission)) }
}
}
- override fun transformItem(app: ApplicationInfo) = AppOpPermissionRecord(
- app = app,
- hasRequestPermission = with(packageManagers) { app.hasRequestPermission(permission) },
- appOpsController = createAppOpsController(app),
- )
-
- private fun createAppOpsController(app: ApplicationInfo) = AppOpsController(
- context = context,
- app = app,
- op = appOp,
- setModeByUid = setModeByUid,
- )
+ override fun transformItem(app: ApplicationInfo) =
+ with(packageManagers) {
+ createRecord(
+ app = app,
+ hasRequestPermission = app.hasRequestPermission(permission),
+ )
+ }
override fun filter(userIdFlow: Flow<Int>, recordListFlow: Flow<List<AppOpPermissionRecord>>) =
recordListFlow.filterItem(::isChangeable)
@@ -95,15 +139,19 @@ abstract class AppOpPermissionListModel(
*/
@Composable
override fun isAllowed(record: AppOpPermissionRecord): State<Boolean?> {
+ if (record.hasRequestBroaderPermission) {
+ // Broader permission trumps the specific permission.
+ return stateOf(true)
+ }
+
val mode = record.appOpsController.mode.observeAsState()
return remember {
derivedStateOf {
when (mode.value) {
null -> null
MODE_ALLOWED -> true
- MODE_DEFAULT -> with(packageManagers) {
- record.app.hasGrantPermission(permission)
- }
+ MODE_DEFAULT ->
+ with(packageManagers) { record.app.hasGrantPermission(permission) }
else -> false
}
}
@@ -111,7 +159,9 @@ abstract class AppOpPermissionListModel(
}
override fun isChangeable(record: AppOpPermissionRecord) =
- record.hasRequestPermission && record.app.packageName !in notChangeablePackages
+ record.hasRequestPermission &&
+ !record.hasRequestBroaderPermission &&
+ record.app.packageName !in notChangeablePackages
override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) {
record.appOpsController.setAllowed(newAllowed)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
index 76cff0bba875..e9fcbd2cfa68 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -81,6 +81,13 @@ internal class TogglePermissionAppInfoPageProvider(
navArgument(USER_ID) { type = NavType.IntType },
)
+ /**
+ * Gets the route prefix to this page.
+ *
+ * Expose route prefix to enable enter from non-SPA pages.
+ */
+ fun getRoutePrefix(permissionType: String) = "$PAGE_NAME/$permissionType"
+
@Composable
fun navigator(permissionType: String, app: ApplicationInfo) =
navigator(route = "$PAGE_NAME/$permissionType/${app.toRoute()}")
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
index ce8fc9df7c38..1ab623076f0a 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
@@ -93,6 +93,14 @@ interface TogglePermissionAppListProvider {
fun getAppListRoute(): String =
TogglePermissionAppListPageProvider.getRoute(permissionType)
+ /**
+ * Gets the route prefix to the toggle permission App Info page.
+ *
+ * Expose route prefix to enable enter from non-SPA pages.
+ */
+ fun getAppInfoRoutePrefix(): String =
+ TogglePermissionAppInfoPageProvider.getRoutePrefix(permissionType)
+
@Composable
fun InfoPageEntryItem(app: ApplicationInfo) {
val listModel = rememberContext(::createModel)
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
index 26174c2e03ca..2d8f0098480c 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
@@ -23,7 +23,9 @@ import android.content.pm.PackageManager
import android.content.pm.PackageManager.ApplicationInfoFlags
import android.content.pm.PackageManager.ResolveInfoFlags
import android.content.pm.ResolveInfo
+import android.content.res.Resources
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.internal.R
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.first
@@ -51,12 +53,18 @@ class AppListRepositoryTest {
private lateinit var context: Context
@Mock
+ private lateinit var resources: Resources
+
+ @Mock
private lateinit var packageManager: PackageManager
private lateinit var repository: AppListRepository
@Before
fun setUp() {
+ whenever(context.resources).thenReturn(resources)
+ whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
+ .thenReturn(emptyArray())
whenever(context.packageManager).thenReturn(packageManager)
whenever(packageManager.getInstalledModules(anyInt())).thenReturn(emptyList())
whenever(
@@ -93,12 +101,61 @@ class AppListRepositoryTest {
}
@Test
- fun loadApps_isDisabledUntilUsed() = runTest {
+ fun loadApps_isHideWhenDisabledPackageAndDisabled() = runTest {
val app = ApplicationInfo().apply {
- packageName = "is.disabled.until.used"
+ packageName = "is.hide.when.disabled"
enabled = false
+ }
+ whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
+ .thenReturn(arrayOf(app.packageName))
+ mockInstalledApplications(listOf(app))
+ val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = false)
+
+ val appListFlow = repository.loadApps(appListConfig)
+
+ assertThat(appListFlow).isEmpty()
+ }
+
+ @Test
+ fun loadApps_isHideWhenDisabledPackageAndDisabledUntilUsed() = runTest {
+ val app = ApplicationInfo().apply {
+ packageName = "is.hide.when.disabled"
+ enabled = true
enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
}
+ whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
+ .thenReturn(arrayOf(app.packageName))
+ mockInstalledApplications(listOf(app))
+ val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = false)
+
+ val appListFlow = repository.loadApps(appListConfig)
+
+ assertThat(appListFlow).isEmpty()
+ }
+
+ @Test
+ fun loadApps_isHideWhenDisabledPackageAndEnabled() = runTest {
+ val app = ApplicationInfo().apply {
+ packageName = "is.hide.when.disabled"
+ enabled = true
+ }
+ whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
+ .thenReturn(arrayOf(app.packageName))
+ mockInstalledApplications(listOf(app))
+ val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = false)
+
+ val appListFlow = repository.loadApps(appListConfig)
+
+ assertThat(appListFlow).containsExactly(app)
+ }
+
+ @Test
+ fun loadApps_disabledByUser() = runTest {
+ val app = ApplicationInfo().apply {
+ packageName = "disabled.by.user"
+ enabled = false
+ enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER
+ }
mockInstalledApplications(listOf(app))
val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = false)
@@ -108,7 +165,7 @@ class AppListRepositoryTest {
}
@Test
- fun loadApps_disabled() = runTest {
+ fun loadApps_disabledButNotByUser() = runTest {
val app = ApplicationInfo().apply {
packageName = "disabled"
enabled = false
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
index b9c875ba803a..f51448744c56 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
@@ -118,7 +118,8 @@ private class TestAppListModel : AppListModel<TestAppRecord> {
override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
appListFlow.mapItem(::TestAppRecord)
- override suspend fun onFirstLoaded(recordList: List<TestAppRecord>) {
+ override suspend fun onFirstLoaded(recordList: List<TestAppRecord>): Boolean {
onFirstLoadedCalled = true
+ return false
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt
index 668bfdfd7e32..53e52d0e02e8 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt
@@ -31,21 +31,18 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
import org.mockito.Spy
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
-import org.mockito.Mockito.`when` as whenever
@RunWith(AndroidJUnit4::class)
class AppOpsControllerTest {
- @get:Rule
- val mockito: MockitoRule = MockitoJUnit.rule()
+ @get:Rule val mockito: MockitoRule = MockitoJUnit.rule()
- @Spy
- private val context: Context = ApplicationProvider.getApplicationContext()
+ @Spy private val context: Context = ApplicationProvider.getApplicationContext()
- @Mock
- private lateinit var appOpsManager: AppOpsManager
+ @Mock private lateinit var appOpsManager: AppOpsManager
@Before
fun setUp() {
@@ -54,7 +51,13 @@ class AppOpsControllerTest {
@Test
fun setAllowed_setToTrue() {
- val controller = AppOpsController(context = context, app = APP, op = OP)
+ val controller =
+ AppOpsController(
+ context = context,
+ app = APP,
+ op = OP,
+ modeForNotAllowed = MODE_ERRORED
+ )
controller.setAllowed(true)
@@ -63,7 +66,13 @@ class AppOpsControllerTest {
@Test
fun setAllowed_setToFalse() {
- val controller = AppOpsController(context = context, app = APP, op = OP)
+ val controller =
+ AppOpsController(
+ context = context,
+ app = APP,
+ op = OP,
+ modeForNotAllowed = MODE_ERRORED
+ )
controller.setAllowed(false)
@@ -73,7 +82,13 @@ class AppOpsControllerTest {
@Test
fun setAllowed_setToTrueByUid() {
val controller =
- AppOpsController(context = context, app = APP, op = OP, setModeByUid = true)
+ AppOpsController(
+ context = context,
+ app = APP,
+ op = OP,
+ modeForNotAllowed = MODE_ERRORED,
+ setModeByUid = true
+ )
controller.setAllowed(true)
@@ -83,7 +98,13 @@ class AppOpsControllerTest {
@Test
fun setAllowed_setToFalseByUid() {
val controller =
- AppOpsController(context = context, app = APP, op = OP, setModeByUid = true)
+ AppOpsController(
+ context = context,
+ app = APP,
+ op = OP,
+ modeForNotAllowed = MODE_ERRORED,
+ setModeByUid = true
+ )
controller.setAllowed(false)
@@ -92,10 +113,15 @@ class AppOpsControllerTest {
@Test
fun getMode() {
- whenever(
- appOpsManager.checkOpNoThrow(OP, APP.uid, APP.packageName)
- ).thenReturn(MODE_ALLOWED)
- val controller = AppOpsController(context = context, app = APP, op = OP)
+ whenever(appOpsManager.checkOpNoThrow(OP, APP.uid, APP.packageName))
+ .thenReturn(MODE_ALLOWED)
+ val controller =
+ AppOpsController(
+ context = context,
+ app = APP,
+ op = OP,
+ modeForNotAllowed = MODE_ERRORED
+ )
val mode = controller.getMode()
@@ -104,9 +130,10 @@ class AppOpsControllerTest {
private companion object {
const val OP = 1
- val APP = ApplicationInfo().apply {
- packageName = "package.name"
- uid = 123
- }
+ val APP =
+ ApplicationInfo().apply {
+ packageName = "package.name"
+ uid = 123
+ }
}
-} \ No newline at end of file
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
index 62413864c7df..06003c0cb8f9 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
@@ -56,7 +56,6 @@ class AppListPageTest {
val state = inputState!!.state
assertThat(state.showSystem.value).isFalse()
- assertThat(state.option.value).isEqualTo(0)
assertThat(state.searchQuery.value).isEqualTo("")
}
@@ -89,38 +88,14 @@ class AppListPageTest {
.assertIsDisplayed()
}
- @Test
- fun whenHasOptions_firstOptionDisplayed() {
- val inputState by setContent(options = listOf(OPTION_0, OPTION_1))
-
- composeTestRule.onNodeWithText(OPTION_0).assertIsDisplayed()
- composeTestRule.onNodeWithText(OPTION_1).assertDoesNotExist()
- val state = inputState!!.state
- assertThat(state.option.value).isEqualTo(0)
- }
-
- @Test
- fun whenHasOptions_couldSwitchOption() {
- val inputState by setContent(options = listOf(OPTION_0, OPTION_1))
-
- composeTestRule.onNodeWithText(OPTION_0).performClick()
- composeTestRule.onNodeWithText(OPTION_1).performClick()
-
- composeTestRule.onNodeWithText(OPTION_1).assertIsDisplayed()
- composeTestRule.onNodeWithText(OPTION_0).assertDoesNotExist()
- val state = inputState!!.state
- assertThat(state.option.value).isEqualTo(1)
- }
-
private fun setContent(
- options: List<String> = emptyList(),
header: @Composable () -> Unit = {},
): State<AppListInput<TestAppRecord>?> {
val appListState = mutableStateOf<AppListInput<TestAppRecord>?>(null)
composeTestRule.setContent {
AppListPage(
title = TITLE,
- listModel = TestAppListModel(options),
+ listModel = TestAppListModel(),
header = header,
appList = { appListState.value = this },
)
@@ -130,7 +105,5 @@ class AppListPageTest {
private companion object {
const val TITLE = "Title"
- const val OPTION_0 = "Option 1"
- const val OPTION_1 = "Option 2"
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
index 0154aa194426..2a1f7a4ad908 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
@@ -24,17 +24,21 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
import androidx.compose.ui.unit.dp
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.framework.compose.toState
+import com.android.settingslib.spa.framework.util.StateFlowBridge
+import com.android.settingslib.spa.widget.ui.SpinnerOption
import com.android.settingslib.spaprivileged.R
import com.android.settingslib.spaprivileged.model.app.AppEntry
import com.android.settingslib.spaprivileged.model.app.AppListConfig
import com.android.settingslib.spaprivileged.model.app.AppListData
+import com.android.settingslib.spaprivileged.model.app.IAppListViewModel
import com.android.settingslib.spaprivileged.tests.testutils.TestAppListModel
import com.android.settingslib.spaprivileged.tests.testutils.TestAppRecord
+import kotlinx.coroutines.flow.flowOf
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -47,6 +51,25 @@ class AppListTest {
private val context: Context = ApplicationProvider.getApplicationContext()
@Test
+ fun whenHasOptions_firstOptionDisplayed() {
+ setContent(options = listOf(OPTION_0, OPTION_1))
+
+ composeTestRule.onNodeWithText(OPTION_0).assertIsDisplayed()
+ composeTestRule.onNodeWithText(OPTION_1).assertDoesNotExist()
+ }
+
+ @Test
+ fun whenHasOptions_couldSwitchOption() {
+ setContent(options = listOf(OPTION_0, OPTION_1))
+
+ composeTestRule.onNodeWithText(OPTION_0).performClick()
+ composeTestRule.onNodeWithText(OPTION_1).performClick()
+
+ composeTestRule.onNodeWithText(OPTION_1).assertIsDisplayed()
+ composeTestRule.onNodeWithText(OPTION_0).assertDoesNotExist()
+ }
+
+ @Test
fun whenNoApps() {
setContent(appEntries = emptyList())
@@ -85,28 +108,37 @@ class AppListTest {
}
private fun setContent(
- appEntries: List<AppEntry<TestAppRecord>>,
+ options: List<String> = emptyList(),
+ appEntries: List<AppEntry<TestAppRecord>> = emptyList(),
header: @Composable () -> Unit = {},
enableGrouping: Boolean = false,
) {
composeTestRule.setContent {
- val appListInput = AppListInput(
+ AppListInput(
config = AppListConfig(userId = USER_ID, showInstantApps = false),
listModel = TestAppListModel(enableGrouping = enableGrouping),
state = AppListState(
showSystem = false.toState(),
- option = 0.toState(),
searchQuery = "".toState(),
),
header = header,
bottomPadding = 0.dp,
- )
- appListInput.AppListImpl { stateOf(AppListData(appEntries, option = 0)) }
+ ).AppListImpl {
+ object : IAppListViewModel<TestAppRecord> {
+ override val option: StateFlowBridge<Int?> = StateFlowBridge()
+ override val spinnerOptionsFlow = flowOf(options.mapIndexed { index, option ->
+ SpinnerOption(id = index, text = option)
+ })
+ override val appListDataFlow = flowOf(AppListData(appEntries, option = 0))
+ }
+ }
}
}
private companion object {
const val USER_ID = 0
+ const val OPTION_0 = "Option 1"
+ const val OPTION_1 = "Option 2"
const val HEADER = "Header"
const val GROUP_A = "Group A"
const val GROUP_B = "Group B"
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
index 966b86927e55..da765ba87e46 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
@@ -39,28 +39,23 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
import org.mockito.Spy
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
-import org.mockito.Mockito.`when` as whenever
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class AppOpPermissionAppListTest {
- @get:Rule
- val mockito: MockitoRule = MockitoJUnit.rule()
+ @get:Rule val mockito: MockitoRule = MockitoJUnit.rule()
- @get:Rule
- val composeTestRule = createComposeRule()
+ @get:Rule val composeTestRule = createComposeRule()
- @Spy
- private val context: Context = ApplicationProvider.getApplicationContext()
+ @Spy private val context: Context = ApplicationProvider.getApplicationContext()
- @Mock
- private lateinit var packageManagers: IPackageManagers
+ @Mock private lateinit var packageManagers: IPackageManagers
- @Mock
- private lateinit var appOpsManager: AppOpsManager
+ @Mock private lateinit var appOpsManager: AppOpsManager
private lateinit var listModel: TestAppOpPermissionAppListModel
@@ -79,9 +74,7 @@ class AppOpPermissionAppListTest {
@Test
fun transformItem_hasRequestPermission() = runTest {
- with(packageManagers) {
- whenever(APP.hasRequestPermission(PERMISSION)).thenReturn(true)
- }
+ with(packageManagers) { whenever(APP.hasRequestPermission(PERMISSION)).thenReturn(true) }
val record = listModel.transformItem(APP)
@@ -90,25 +83,47 @@ class AppOpPermissionAppListTest {
@Test
fun transformItem_notRequestPermission() = runTest {
+ with(packageManagers) { whenever(APP.hasRequestPermission(PERMISSION)).thenReturn(false) }
+
+ val record = listModel.transformItem(APP)
+
+ assertThat(record.hasRequestPermission).isFalse()
+ }
+
+ @Test
+ fun transformItem_hasRequestBroaderPermission() = runTest {
+ listModel.broaderPermission = BROADER_PERMISSION
with(packageManagers) {
- whenever(APP.hasRequestPermission(PERMISSION)).thenReturn(false)
+ whenever(APP.hasRequestPermission(BROADER_PERMISSION)).thenReturn(true)
}
val record = listModel.transformItem(APP)
- assertThat(record.hasRequestPermission).isFalse()
+ assertThat(record.hasRequestBroaderPermission).isTrue()
}
@Test
- fun filter() = runTest {
+ fun transformItem_notRequestBroaderPermission() = runTest {
+ listModel.broaderPermission = BROADER_PERMISSION
with(packageManagers) {
- whenever(APP.hasRequestPermission(PERMISSION)).thenReturn(false)
+ whenever(APP.hasRequestPermission(BROADER_PERMISSION)).thenReturn(false)
}
- val record = AppOpPermissionRecord(
- app = APP,
- hasRequestPermission = false,
- appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
- )
+
+ val record = listModel.transformItem(APP)
+
+ assertThat(record.hasRequestPermission).isFalse()
+ }
+
+ @Test
+ fun filter() = runTest {
+ with(packageManagers) { whenever(APP.hasRequestPermission(PERMISSION)).thenReturn(false) }
+ val record =
+ AppOpPermissionRecord(
+ app = APP,
+ hasRequestBroaderPermission = false,
+ hasRequestPermission = false,
+ appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+ )
val recordListFlow = listModel.filter(flowOf(USER_ID), flowOf(listOf(record)))
@@ -118,11 +133,13 @@ class AppOpPermissionAppListTest {
@Test
fun isAllowed_allowed() {
- val record = AppOpPermissionRecord(
- app = APP,
- hasRequestPermission = true,
- appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ALLOWED),
- )
+ val record =
+ AppOpPermissionRecord(
+ app = APP,
+ hasRequestBroaderPermission = false,
+ hasRequestPermission = true,
+ appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ALLOWED),
+ )
val isAllowed = getIsAllowed(record)
@@ -131,14 +148,14 @@ class AppOpPermissionAppListTest {
@Test
fun isAllowed_defaultAndHasGrantPermission() {
- with(packageManagers) {
- whenever(APP.hasGrantPermission(PERMISSION)).thenReturn(true)
- }
- val record = AppOpPermissionRecord(
- app = APP,
- hasRequestPermission = true,
- appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
- )
+ with(packageManagers) { whenever(APP.hasGrantPermission(PERMISSION)).thenReturn(true) }
+ val record =
+ AppOpPermissionRecord(
+ app = APP,
+ hasRequestBroaderPermission = false,
+ hasRequestPermission = true,
+ appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+ )
val isAllowed = getIsAllowed(record)
@@ -147,27 +164,49 @@ class AppOpPermissionAppListTest {
@Test
fun isAllowed_defaultAndNotGrantPermission() {
+ with(packageManagers) { whenever(APP.hasGrantPermission(PERMISSION)).thenReturn(false) }
+ val record =
+ AppOpPermissionRecord(
+ app = APP,
+ hasRequestBroaderPermission = false,
+ hasRequestPermission = true,
+ appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+ )
+
+ val isAllowed = getIsAllowed(record)
+
+ assertThat(isAllowed).isFalse()
+ }
+
+ @Test
+ fun isAllowed_broaderPermissionTrumps() {
+ listModel.broaderPermission = BROADER_PERMISSION
with(packageManagers) {
whenever(APP.hasGrantPermission(PERMISSION)).thenReturn(false)
+ whenever(APP.hasGrantPermission(BROADER_PERMISSION)).thenReturn(true)
}
- val record = AppOpPermissionRecord(
- app = APP,
- hasRequestPermission = true,
- appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
- )
+ val record =
+ AppOpPermissionRecord(
+ app = APP,
+ hasRequestBroaderPermission = true,
+ hasRequestPermission = false,
+ appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ERRORED),
+ )
val isAllowed = getIsAllowed(record)
- assertThat(isAllowed).isFalse()
+ assertThat(isAllowed).isTrue()
}
@Test
fun isAllowed_notAllowed() {
- val record = AppOpPermissionRecord(
- app = APP,
- hasRequestPermission = true,
- appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ERRORED),
- )
+ val record =
+ AppOpPermissionRecord(
+ app = APP,
+ hasRequestBroaderPermission = false,
+ hasRequestPermission = true,
+ appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ERRORED),
+ )
val isAllowed = getIsAllowed(record)
@@ -176,11 +215,13 @@ class AppOpPermissionAppListTest {
@Test
fun isChangeable_notRequestPermission() {
- val record = AppOpPermissionRecord(
- app = APP,
- hasRequestPermission = false,
- appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
- )
+ val record =
+ AppOpPermissionRecord(
+ app = APP,
+ hasRequestBroaderPermission = false,
+ hasRequestPermission = false,
+ appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+ )
val isChangeable = listModel.isChangeable(record)
@@ -189,11 +230,13 @@ class AppOpPermissionAppListTest {
@Test
fun isChangeable_notChangeablePackages() {
- val record = AppOpPermissionRecord(
- app = NOT_CHANGEABLE_APP,
- hasRequestPermission = true,
- appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
- )
+ val record =
+ AppOpPermissionRecord(
+ app = NOT_CHANGEABLE_APP,
+ hasRequestBroaderPermission = false,
+ hasRequestPermission = true,
+ appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+ )
val isChangeable = listModel.isChangeable(record)
@@ -202,11 +245,13 @@ class AppOpPermissionAppListTest {
@Test
fun isChangeable_hasRequestPermissionAndChangeable() {
- val record = AppOpPermissionRecord(
- app = APP,
- hasRequestPermission = true,
- appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
- )
+ val record =
+ AppOpPermissionRecord(
+ app = APP,
+ hasRequestBroaderPermission = false,
+ hasRequestPermission = true,
+ appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+ )
val isChangeable = listModel.isChangeable(record)
@@ -214,13 +259,31 @@ class AppOpPermissionAppListTest {
}
@Test
+ fun isChangeable_broaderPermissionTrumps() {
+ listModel.broaderPermission = BROADER_PERMISSION
+ val record =
+ AppOpPermissionRecord(
+ app = APP,
+ hasRequestBroaderPermission = true,
+ hasRequestPermission = true,
+ appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+ )
+
+ val isChangeable = listModel.isChangeable(record)
+
+ assertThat(isChangeable).isFalse()
+ }
+
+ @Test
fun setAllowed() {
val appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT)
- val record = AppOpPermissionRecord(
- app = APP,
- hasRequestPermission = true,
- appOpsController = appOpsController,
- )
+ val record =
+ AppOpPermissionRecord(
+ app = APP,
+ hasRequestBroaderPermission = false,
+ hasRequestPermission = true,
+ appOpsController = appOpsController,
+ )
listModel.setAllowed(record = record, newAllowed = true)
@@ -239,9 +302,7 @@ class AppOpPermissionAppListTest {
private fun getIsAllowed(record: AppOpPermissionRecord): Boolean? {
lateinit var isAllowedState: State<Boolean?>
- composeTestRule.setContent {
- isAllowedState = listModel.isAllowed(record)
- }
+ composeTestRule.setContent { isAllowedState = listModel.isAllowed(record) }
return isAllowedState.value
}
@@ -250,8 +311,12 @@ class AppOpPermissionAppListTest {
override val pageTitleResId = R.string.test_app_op_permission_title
override val switchTitleResId = R.string.test_app_op_permission_switch_title
override val footerResId = R.string.test_app_op_permission_footer
+
override val appOp = AppOpsManager.OP_MANAGE_MEDIA
override val permission = PERMISSION
+ override val permissionHasAppopFlag = true
+ override var broaderPermission: String? = null
+
override var setModeByUid = false
}
@@ -259,12 +324,9 @@ class AppOpPermissionAppListTest {
const val USER_ID = 0
const val PACKAGE_NAME = "package.name"
const val PERMISSION = "PERMISSION"
- val APP = ApplicationInfo().apply {
- packageName = PACKAGE_NAME
- }
- val NOT_CHANGEABLE_APP = ApplicationInfo().apply {
- packageName = "android"
- }
+ const val BROADER_PERMISSION = "BROADER_PERMISSION"
+ val APP = ApplicationInfo().apply { packageName = PACKAGE_NAME }
+ val NOT_CHANGEABLE_APP = ApplicationInfo().apply { packageName = "android" }
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt
index ada4016bea13..a7a153ba479c 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt
@@ -28,11 +28,8 @@ data class TestAppRecord(
) : AppRecord
class TestAppListModel(
- private val options: List<String> = emptyList(),
private val enableGrouping: Boolean = false,
) : AppListModel<TestAppRecord> {
- override fun getSpinnerOptions() = options
-
override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
appListFlow.mapItem(::TestAppRecord)
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 4a1a4e61f887..810545c40738 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -291,6 +291,18 @@ public class BluetoothUtils {
return false;
}
+ /**
+ * Check if a device class matches with a defined BluetoothClass device.
+ *
+ * @param device Must be one of the public constants in {@link BluetoothClass.Device}
+ * @return true if device class matches, false otherwise.
+ */
+ public static boolean isDeviceClassMatched(@NonNull BluetoothDevice bluetoothDevice,
+ int device) {
+ return bluetoothDevice.getBluetoothClass() != null
+ && bluetoothDevice.getBluetoothClass().getDeviceClass() == device;
+ }
+
private static boolean isAdvancedHeaderEnabled() {
if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI, BT_ADVANCED_HEADER_ENABLED,
true)) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 2951001f5368..c2c1b551470b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -1028,12 +1028,11 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
if (BluetoothUuid.containsAnyUuid(uuids, PbapServerProfile.PBAB_CLIENT_UUIDS)) {
// The pairing dialog now warns of phone-book access for paired devices.
// No separate prompt is displayed after pairing.
- final BluetoothClass bluetoothClass = mDevice.getBluetoothClass();
if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_UNKNOWN) {
- if (bluetoothClass != null && (bluetoothClass.getDeviceClass()
- == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE
- || bluetoothClass.getDeviceClass()
- == BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET)) {
+ if (BluetoothUtils.isDeviceClassMatched(mDevice,
+ BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE)
+ || BluetoothUtils.isDeviceClassMatched(mDevice,
+ BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET)) {
EventLog.writeEvent(0x534e4554, "138529441", -1, "");
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 221836b02347..f741f655bf7c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -390,7 +390,10 @@ public class CachedBluetoothDeviceManager {
Log.d(TAG, "Bond " + device.getAnonymizedAddress() + " by CSIP");
mOngoingSetMemberPair = device;
syncConfigFromMainDevice(device, groupId);
- device.createBond(BluetoothDevice.TRANSPORT_LE);
+ if (!device.createBond(BluetoothDevice.TRANSPORT_LE)) {
+ Log.d(TAG, "Bonding could not be started");
+ mOngoingSetMemberPair = null;
+ }
}
private void syncConfigFromMainDevice(BluetoothDevice device, int groupId) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java
index feb5e0bf6e69..1401a4f923b6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java
@@ -41,8 +41,8 @@ public final class HearingAidStatsLogUtils {
}
/**
- * Logs hearing aid device information to westworld, including device mode, device side, and
- * entry page id where the binding(connecting) process starts.
+ * Logs hearing aid device information to statsd, including device mode, device side, and entry
+ * page id where the binding(connecting) process starts.
*
* Only logs the info once after hearing aid is bonded(connected). Clears the map entry of this
* device when logging is completed.
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
index 562d4800cc45..e781f1307072 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
@@ -275,6 +275,10 @@ public class LeAudioProfile implements LocalBluetoothProfile {
}
public int getDrawableResource(BluetoothClass btClass) {
+ if (btClass == null) {
+ Log.e(TAG, "No btClass.");
+ return R.drawable.ic_bt_le_audio_speakers;
+ }
switch (btClass.getDeviceClass()) {
case BluetoothClass.Device.AUDIO_VIDEO_UNCATEGORIZED:
case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index 28286817ea7e..aa6aaaf352cb 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -59,6 +59,7 @@ public class GlobalSettings {
Settings.Global.ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS,
Settings.Global.ENCODED_SURROUND_OUTPUT,
Settings.Global.ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS,
+ Settings.Global.LOW_POWER_MODE_REMINDER_ENABLED,
Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL,
Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED,
Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 46876b457b54..2b0d8370bb01 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -132,6 +132,7 @@ public class GlobalSettingsValidators {
new DiscreteValueValidator(new String[] {"0", "1"}));
VALIDATORS.put(Global.LOW_POWER_MODE_TRIGGER_LEVEL, PERCENTAGE_INTEGER_VALIDATOR);
VALIDATORS.put(Global.LOW_POWER_MODE_TRIGGER_LEVEL_MAX, PERCENTAGE_INTEGER_VALIDATOR);
+ VALIDATORS.put(Global.LOW_POWER_MODE_REMINDER_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(
Global.AUTOMATIC_POWER_SAVE_MODE,
new DiscreteValueValidator(new String[] {"0", "1"}));
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index af4d7d46f320..a418c204f6b5 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -24,6 +24,7 @@
<!-- Standard permissions granted to the shell. -->
<uses-permission android:name="android.permission.MANAGE_HEALTH_DATA" />
+ <uses-permission android:name="android.permission.MIGRATE_HEALTH_CONNECT_DATA" />
<uses-permission android:name="android.permission.LAUNCH_DEVICE_MANAGER_SETUP" />
<uses-permission android:name="android.permission.GET_RUNTIME_PERMISSIONS" />
<uses-permission android:name="android.permission.SEND_SMS" />
@@ -794,6 +795,9 @@
<!-- Permission required for CTS test - CtsPackageInstallTestCases-->
<uses-permission android:name="android.permission.GET_APP_METADATA" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED"/>
+
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:defaultToDeviceProtectedStorage="true"
@@ -871,6 +875,7 @@
<service
android:name=".BugreportProgressService"
+ android:foregroundServiceType="systemExempted"
android:exported="false"/>
</application>
</manifest>
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index cefcf066d641..2c9dad96b076 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -170,6 +170,7 @@
<!-- Screen Recording -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT"/>
@@ -414,7 +415,8 @@
android:process=":screenshot_cross_profile"
android:exported="false" />
- <service android:name=".screenrecord.RecordingService" />
+ <service android:name=".screenrecord.RecordingService"
+ android:foregroundServiceType="systemExempted"/>
<receiver android:name=".SysuiRestartReceiver"
android:exported="false">
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index f92625bd9a73..b22195a99eb2 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -32,18 +32,19 @@
}
]
},
- {
- // TODO(b/251476085): Consider merging with SystemUIGoogleScreenshotTests (in U+)
- "name": "SystemUIGoogleBiometricsScreenshotTests",
- "options": [
- {
- "exclude-annotation": "org.junit.Ignore"
- },
- {
- "exclude-annotation": "androidx.test.filters.FlakyTest"
- }
- ]
- },
+// Disable until can pass: b/259124654
+// {
+// // TODO(b/251476085): Consider merging with SystemUIGoogleScreenshotTests (in U+)
+// "name": "SystemUIGoogleBiometricsScreenshotTests",
+// "options": [
+// {
+// "exclude-annotation": "org.junit.Ignore"
+// },
+// {
+// "exclude-annotation": "androidx.test.filters.FlakyTest"
+// }
+// ]
+// },
{
// Permission indicators
"name": "CtsPermission4TestCases",
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
new file mode 100644
index 000000000000..a494f5e086ae
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
@@ -0,0 +1,32 @@
+//
+// 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.
+//
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_app {
+ name: "AccessibilityMenu",
+ srcs: [
+ "src/**/*.java",
+ ],
+ system_ext_specific: true,
+ platform_apis: true,
+ resource_dirs: ["res"],
+ certificate: "platform",
+ // This app uses allowlisted privileged permissions.
+ privileged: true,
+}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
new file mode 100644
index 000000000000..26748a92c4ed
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.systemui.accessibility.accessibilitymenu">
+ <application>
+ <service
+ android:name="com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService"
+ android:exported="false"
+ android:label="Accessibility Menu (System)"
+ android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+ <intent-filter>
+ <action android:name="android.accessibilityservice.AccessibilityService"/>
+ </intent-filter>
+ <meta-data
+ android:name="android.accessibilityservice"
+ android:resource="@xml/accessibilitymenu_service"/>
+ </service>
+ </application>
+</manifest> \ No newline at end of file
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/OWNERS b/packages/SystemUI/accessibility/accessibilitymenu/OWNERS
new file mode 100644
index 000000000000..b74281edbf52
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/view/accessibility/OWNERS
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/xml/accessibilitymenu_service.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/xml/accessibilitymenu_service.xml
new file mode 100644
index 000000000000..96882d335d4b
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/xml/accessibilitymenu_service.xml
@@ -0,0 +1,16 @@
+<?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.
+-->
+<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"/> \ No newline at end of file
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
new file mode 100644
index 000000000000..8b759004f657
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.accessibility.accessibilitymenu;
+
+import android.accessibilityservice.AccessibilityService;
+import android.view.accessibility.AccessibilityEvent;
+
+/** @hide */
+public class AccessibilityMenuService extends AccessibilityService {
+
+ @Override
+ public void onAccessibilityEvent(AccessibilityEvent event) {
+ }
+
+ @Override
+ public void onInterrupt() {
+ }
+}
diff --git a/packages/SystemUI/docs/demo_mode.md b/packages/SystemUI/docs/demo_mode.md
index 6cf7060c794c..b2424f42bf43 100644
--- a/packages/SystemUI/docs/demo_mode.md
+++ b/packages/SystemUI/docs/demo_mode.md
@@ -22,42 +22,42 @@ intent.
<br/>
Commands are sent as string extras with key ```command``` (required). Possible values are:
-Command | Subcommand | Argument | Description
---- | --- | --- | ---
-```enter``` | | | Enters demo mode, bar state allowed to be modified (for convenience, any of the other non-exit commands will automatically flip demo mode on, no need to call this explicitly in practice)
-```exit``` | | | Exits demo mode, bars back to their system-driven state
-```battery``` | | | Control the battery display
- | ```level``` | | Sets the battery level (0 - 100)
- | ```plugged``` | | Sets charging state (```true```, ```false```)
- | ```powersave``` | | Sets power save mode (```true```, ```anything else```)
-```network``` | | | Control the RSSI display
- | ```airplane``` | | ```show``` to show icon, any other value to hide
- | ```fully``` | | Sets MCS state to fully connected (```true```, ```false```)
- | ```wifi``` | | ```show``` to show icon, any other value to hide
- | | ```level``` | Sets wifi level (null or 0-4)
- | ```mobile``` | | ```show``` to show icon, any other value to hide
- | | ```datatype``` | Values: ```1x```, ```3g```, ```4g```, ```e```, ```g```, ```h```, ```lte```, ```roam```, any other value to hide
- | | ```level``` | Sets mobile signal strength level (null or 0-4)
- | ```carriernetworkchange``` | | Sets mobile signal icon to carrier network change UX when disconnected (```show``` to show icon, any other value to hide)
- | ```sims``` | | Sets the number of sims (1-8)
- | ```nosim``` | | ```show``` to show icon, any other value to hide
-```bars``` | | | Control the visual style of the bars (opaque, translucent, etc)
- | ```mode``` | | Sets the bars visual style (opaque, translucent, semi-transparent)
-```status``` | | | Control the system status icons
- | ```volume``` | | Sets the icon in the volume slot (```silent```, ```vibrate```, any other value to hide)
- | ```bluetooth``` | | Sets the icon in the bluetooth slot (```connected```, ```disconnected```, any other value to hide)
- | ```location``` | | Sets the icon in the location slot (```show```, any other value to hide)
- | ```alarm``` | | Sets the icon in the alarm_clock slot (```show```, any other value to hide)
- | ```sync``` | | Sets the icon in the sync_active slot (```show```, any other value to hide)
- | ```tty``` | | Sets the icon in the tty slot (```show```, any other value to hide)
- | ```eri``` | | Sets the icon in the cdma_eri slot (```show```, any other value to hide)
- | ```mute``` | | Sets the icon in the mute slot (```show```, any other value to hide)
- | ```speakerphone``` | | Sets the icon in the speakerphone slot (```show```, any other value to hide)
-```notifications``` | | | Control the notification icons
- | ```visible``` | | ```false``` to hide the notification icons, any other value to show
-```clock``` | | | Control the clock display
- | ```millis``` | | Sets the time in millis
- | ```hhmm``` | | Sets the time in hh:mm
+| Command | Subcommand | Argument | Description
+| --- | --- | --- | ---
+| ```enter``` | | | Enters demo mode, bar state allowed to be modified (for convenience, any of the other non-exit commands will automatically flip demo mode on, no need to call this explicitly in practice)
+| ```exit``` | | | Exits demo mode, bars back to their system-driven state
+| ```battery``` | | | Control the battery display
+| | ```level``` | | Sets the battery level (0 - 100)
+| | ```plugged``` | | Sets charging state (```true```, ```false```)
+| | ```powersave``` | | Sets power save mode (```true```, ```anything else```)
+| ```network``` | | | Control the RSSI display
+| | ```airplane``` | | ```show``` to show icon, any other value to hide
+| | ```fully``` | | Sets MCS state to fully connected (```true```, ```false```)
+| | ```wifi``` | | ```show``` to show icon, any other value to hide
+| | | ```level``` | Sets wifi level (null or 0-4)
+| | ```mobile``` | | ```show``` to show icon, any other value to hide
+| | | ```datatype``` | Values: ```1x```, ```3g```, ```4g```, ```e```, ```g```, ```h```, ```lte```, ```roam```, any other value to hide
+| | | ```level``` | Sets mobile signal strength level (null or 0-4)
+| | ```carriernetworkchange``` | | Sets mobile signal icon to carrier network change UX when disconnected (```show``` to show icon, any other value to hide)
+| | ```sims``` | | Sets the number of sims (1-8)
+| | ```nosim``` | | ```show``` to show icon, any other value to hide
+| ```bars``` | | | Control the visual style of the bars (opaque, translucent, etc)
+| | ```mode``` | | Sets the bars visual style (opaque, translucent, semi-transparent)
+| ```status``` | | | Control the system status icons
+| | ```volume``` | | Sets the icon in the volume slot (```silent```, ```vibrate```, any other value to hide)
+| | ```bluetooth``` | | Sets the icon in the bluetooth slot (```connected```, ```disconnected```, any other value to hide)
+| | ```location``` | | Sets the icon in the location slot (```show```, any other value to hide)
+| | ```alarm``` | | Sets the icon in the alarm_clock slot (```show```, any other value to hide)
+| | ```sync``` | | Sets the icon in the sync_active slot (```show```, any other value to hide)
+| | ```tty``` | | Sets the icon in the tty slot (```show```, any other value to hide)
+| | ```eri``` | | Sets the icon in the cdma_eri slot (```show```, any other value to hide)
+| | ```mute``` | | Sets the icon in the mute slot (```show```, any other value to hide)
+| | ```speakerphone``` | | Sets the icon in the speakerphone slot (```show```, any other value to hide)
+| ```notifications``` | | | Control the notification icons
+| | ```visible``` | | ```false``` to hide the notification icons, any other value to show
+| ```clock``` | | | Control the clock display
+| | ```millis``` | | Sets the time in millis
+| | ```hhmm``` | | Sets the time in hh:mm
## Examples
Enter demo mode
diff --git a/packages/SystemUI/docs/modern-architecture.png b/packages/SystemUI/docs/modern-architecture.png
new file mode 100644
index 000000000000..2636362dd2ec
--- /dev/null
+++ b/packages/SystemUI/docs/modern-architecture.png
Binary files differ
diff --git a/packages/SystemUI/docs/status-bar-data-pipeline.md b/packages/SystemUI/docs/status-bar-data-pipeline.md
new file mode 100644
index 000000000000..9fcdce1ef717
--- /dev/null
+++ b/packages/SystemUI/docs/status-bar-data-pipeline.md
@@ -0,0 +1,261 @@
+# Status Bar Data Pipeline
+
+## Background
+
+The status bar is the UI shown at the top of the user's screen that gives them
+information about the time, notifications, and system status like mobile
+conectivity and battery level. This document is about the implementation of the
+wifi and mobile system icons on the right side:
+
+![image of status bar](status-bar.png)
+
+In Android U, the data pipeline that determines what mobile and wifi icons to
+show in the status bar has been re-written with a new architecture. This format
+generally follows Android best practices to
+[app architecture](https://developer.android.com/topic/architecture#recommended-app-arch).
+This document serves as a guide for the new architecture, and as a guide for how
+OEMs can add customizations to the new architecture.
+
+## Architecture
+
+In the new architecture, there is a separate pipeline for each type of icon. For
+Android U, **only the wifi icon and mobile icons have been implemented in the
+new architecture**.
+
+As shown in the Android best practices guide, each new pipeline has a data
+layer, a domain layer, and a UI layer:
+
+![diagram of UI, domain, and data layers](modern-architecture.png)
+
+The classes in the data layer are `repository` instances. The classes in the
+domain layer are `interactor` instances. The classes in the UI layer are
+`viewmodel` instances and `viewbinder` instances. In this document, "repository"
+and "data layer" will be used interchangably (and the same goes for the other
+layers).
+
+The wifi logic is in `statusbar/pipeline/wifi` and the mobile logic is in
+`statusbar/pipeline/mobile`.
+
+#### Repository (data layer)
+
+System callbacks, broadcast receivers, configuration values are all defined
+here, and exposed through the appropriate interface. Where appropriate, we
+define `Model` objects at this layer so that clients do not have to rely on
+system-defined interfaces.
+
+#### Interactor (domain layer)
+
+Here is where we define the business logic and transform the data layer objects
+into something consumable by the ViewModel classes. For example,
+`MobileIconsInteractor` defines the CBRS filtering logic by exposing a
+`filteredSubscriptions` list.
+
+#### ViewModel (UI layer)
+
+View models should define the final piece of business logic mapping to UI logic.
+For example, the mobile view model checks the `IconInteractor.isRoaming` flow to
+decide whether or not to show the roaming indicator.
+
+#### ViewBinder
+
+These have already been implemented and configured. ViewBinders replace the old
+`applyMobileState` mechanism that existed in the `IconManager` classes of the
+old pipeline. A view binder associates a ViewModel with a View, and keeps the
+view up-to-date with the most recent information from the model.
+
+Any new fields added to the ViewModel classes need to be equivalently bound to
+the view here.
+
+### Putting it all together
+
+Putting that altogether, we have this overall architecture diagram for the
+icons:
+
+![diagram of wifi and mobile pipelines](status-bar-pipeline.png)
+
+### Mobile icons architecture
+
+Because there can be multiple mobile connections at the same time, the mobile
+pipeline is split up hierarchically. At each level (data, domain, and UI), there
+is a singleton parent class that manages information relevant to **all** mobile
+connections, and multiple instances of child classes that manage information for
+a **single** mobile connection.
+
+For example, `MobileConnectionsRepository` is a singleton at the data layer that
+stores information relevant to **all** mobile connections, and it also manages a
+list of child `MobileConnectionRepository` classes. `MobileConnectionRepository`
+is **not** a singleton, and each individual `MobileConnectionRepository`
+instance fully qualifies the state of a **single** connection. This pattern is
+repeated at the `Interactor` and `ViewModel` layers for mobile.
+
+![diagram of mobile parent child relationship](status-bar-mobile-pipeline.png)
+
+Note: Since there is at most one wifi connection, the wifi pipeline is not split
+up in the same way.
+
+## Customizations
+
+The new pipeline completely replaces these classes:
+
+* `WifiStatusTracker`
+* `MobileStatusTracker`
+* `NetworkSignalController` and `NetworkSignalControllerImpl`
+* `MobileSignalController`
+* `WifiSignalController`
+* `StatusBarSignalPolicy` (including `SignalIconState`, `MobileIconState`, and
+ `WifiIconState`)
+
+Any customizations in any of these classes will need to be migrated to the new
+pipeline. As a general rule, any change that would have gone into
+`NetworkControllerImpl` would be done in `MobileConnectionsRepository`, and any
+change for `MobileSignalController` can be done in `MobileConnectionRepository`
+(see above on the relationship between those repositories).
+
+### Sample customization: New service
+
+Some customizations require listening to additional services to get additional
+data. This new architecture makes it easy to add additional services to the
+status bar data pipeline to get icon customizations.
+
+Below is a general guide to how a new service should be added. However, there
+may be exceptions to this guide for specific use cases.
+
+1. In the data layer (`repository` classes), add a new `StateFlow` that listens
+ to the service:
+
+ ```kotlin
+ class MobileConnectionsRepositoryImpl {
+ ...
+ val fooVal: StateFlow<Int> =
+ conflatedCallbackFlow {
+ val callback = object : FooServiceCallback(), FooListener {
+ override fun onFooChanged(foo: Int) {
+ trySend(foo)
+ }
+ }
+
+ fooService.registerCallback(callback)
+
+ awaitClose { fooService.unregisterCallback(callback) }
+ }
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), FOO_DEFAULT_VAL)
+ }
+ ```
+
+1. In the domain layer (`interactor` classes), either use this new flow to
+ process values, or just expose the flow as-is for the UI layer.
+
+ For example, if `bar` should only be true when `foo` is positive:
+
+ ```kotlin
+ class MobileIconsInteractor {
+ ...
+ val bar: StateFlow<Boolean> =
+ mobileConnectionsRepo
+ .mapLatest { foo -> foo > 0 }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = false)
+ }
+ ```
+
+1. In the UI layer (`viewmodel` classes), update the existing flows to process
+ the new value from the interactor.
+
+ For example, if the icon should be hidden when `bar` is true:
+
+ ```kotlin
+ class MobileIconViewModel {
+ ...
+ iconId: Flow<Int> = combine(
+ iconInteractor.level,
+ iconInteractor.numberOfLevels,
+ iconInteractor.bar,
+ ) { level, numberOfLevels, bar ->
+ if (bar) {
+ null
+ } else {
+ calcIcon(level, numberOfLevels)
+ }
+ }
+ ```
+
+## Demo mode
+
+SystemUI demo mode is a first-class citizen in the new pipeline. It is
+implemented as an entirely separate repository,
+`DemoMobileConnectionsRepository`. When the system moves into demo mode, the
+implementation of the data layer is switched to the demo repository via the
+`MobileRepositorySwitcher` class.
+
+Because the demo mode repositories implement the same interfaces as the
+production classes, any changes made above will have to be implemented for demo
+mode as well.
+
+1. Following from above, if `fooVal` is added to the
+ `MobileConnectionsRepository` interface:
+
+ ```kotlin
+ class DemoMobileConnectionsRepository {
+ private val _fooVal = MutableStateFlow(FOO_DEFAULT_VALUE)
+ override val fooVal: StateFlow<Int> = _fooVal.asStateFlow()
+
+ // Process the state. **See below on how to add the command to the CLI**
+ fun processEnabledMobileState(state: Mobile) {
+ ...
+ _fooVal.value = state.fooVal
+ }
+ }
+ ```
+
+1. (Optional) If you want to enable the command line interface for setting and
+ testing this value in demo mode, you can add parsing logic to
+ `DemoModeMobileConnectionDataSource` and `FakeNetworkEventModel`:
+
+ ```kotlin
+ sealed interface FakeNetworkEventModel {
+ data class Mobile(
+ ...
+ // Add new fields here
+ val fooVal: Int?
+ )
+ }
+ ```
+
+ ```kotlin
+ class DemoModeMobileConnectionDataSource {
+ // Currently, the demo commands are implemented as an extension function on Bundle
+ private fun Bundle.activeMobileEvent(): Mobile {
+ ...
+ val fooVal = getString("fooVal")?.toInt()
+ return Mobile(
+ ...
+ fooVal = fooVal,
+ )
+ }
+ }
+ ```
+
+If step 2 is implemented, then you will be able to pass demo commands via the
+command line:
+
+```
+adb shell am broadcast -a com.android.systemui.demo -e command network -e mobile show -e fooVal <test value>
+```
+
+## Migration plan
+
+For Android U, the new pipeline will be enabled and default. However, the old
+pipeline code will still be around just in case the new pipeline doesn’t do well
+in the testing phase.
+
+For Android V, the old pipeline will be completely removed and the new pipeline
+will be the one source of truth.
+
+Our ask for OEMs is to default to using the new pipeline in Android U. If there
+are customizations that seem difficult to migrate over to the new pipeline,
+please file a bug with us and we’d be more than happy to consult on the best
+solution. The new pipeline was designed with customizability in mind, so our
+hope is that working the new pipeline will be easier and faster.
+
+Note: The new pipeline currently only supports the wifi and mobile icons. The
+other system status bar icons may be migrated to a similar architecture in the
+future.
diff --git a/packages/SystemUI/docs/status-bar-mobile-pipeline.png b/packages/SystemUI/docs/status-bar-mobile-pipeline.png
new file mode 100644
index 000000000000..620563de3daa
--- /dev/null
+++ b/packages/SystemUI/docs/status-bar-mobile-pipeline.png
Binary files differ
diff --git a/packages/SystemUI/docs/status-bar-pipeline.png b/packages/SystemUI/docs/status-bar-pipeline.png
new file mode 100644
index 000000000000..1c568c9bcda9
--- /dev/null
+++ b/packages/SystemUI/docs/status-bar-pipeline.png
Binary files differ
diff --git a/packages/SystemUI/docs/status-bar.png b/packages/SystemUI/docs/status-bar.png
new file mode 100644
index 000000000000..3a5af0e2c3e0
--- /dev/null
+++ b/packages/SystemUI/docs/status-bar.png
Binary files differ
diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt
index 7243ca4cece0..4c271ea6a464 100644
--- a/packages/SystemUI/ktfmt_includes.txt
+++ b/packages/SystemUI/ktfmt_includes.txt
@@ -460,7 +460,6 @@
-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt
-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt
-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
@@ -486,7 +485,6 @@
-packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt
-packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt
-packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
--packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
-packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarRootView.kt
-packages/SystemUI/src/com/android/systemui/toast/ToastDefaultAnimation.kt
-packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 1ef523bf55a3..7073f6ace7cd 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -661,7 +661,9 @@
<item>7</item> <!-- WAKE_REASON_WAKE_MOTION -->
<item>9</item> <!-- WAKE_REASON_LID -->
<item>10</item> <!-- WAKE_REASON_DISPLAY_GROUP_ADDED -->
- <item>12</item> <!-- WAKE_REASON_UNFOLD_DEVICE -->
+ <item>15</item> <!-- WAKE_REASON_TAP -->
+ <item>16</item> <!-- WAKE_REASON_LIFT -->
+ <item>17</item> <!-- WAKE_REASON_BIOMETRIC -->
</integer-array>
<!-- Whether the communal service should be enabled -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 07e8badda230..45147ca13238 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2301,13 +2301,13 @@
<string name="accessibility_floating_button_docking_tooltip">Move button to the edge to hide it temporarily</string>
<!-- Text for the undo action button of the message view of the accessibility floating menu to perform undo operation. [CHAR LIMIT=30]-->
<string name="accessibility_floating_button_undo">Undo</string>
-
+ <!-- Text for the message view with undo action of the accessibility floating menu to show which feature shortcut was removed. [CHAR LIMIT=30]-->
+ <string name="accessibility_floating_button_undo_message_label_text"><xliff:g id="feature name" example="Magnification">%s</xliff:g> shortcut removed</string>
<!-- Text for the message view with undo action of the accessibility floating menu to show how many features shortcuts were removed. [CHAR LIMIT=30]-->
- <string name="accessibility_floating_button_undo_message_text">{count, plural,
- =1 {{label} shortcut removed}
+ <string name="accessibility_floating_button_undo_message_number_text">{count, plural,
+ =1 {# shortcut removed}
other {# shortcuts removed}
}</string>
-
<!-- Action in accessibility menu to move the accessibility floating button to the top left of the screen. [CHAR LIMIT=30] -->
<string name="accessibility_floating_button_action_move_top_left">Move top left</string>
<!-- Action in accessibility menu to move the accessibility floating button to the top right of the screen. [CHAR LIMIT=30] -->
@@ -2466,6 +2466,8 @@
<string name="media_transfer_playing_different_device">Playing on <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g></string>
<!-- Text informing the user that the media transfer has failed because something went wrong. [CHAR LIsMIT=50] -->
<string name="media_transfer_failed">Something went wrong. Try again.</string>
+ <!-- Text to indicate that a media transfer is currently in-progress, aka loading. [CHAR LIMIT=NONE] -->
+ <string name="media_transfer_loading">Loading</string>
<!-- Error message indicating that a control timed out while waiting for an update [CHAR_LIMIT=30] -->
<string name="controls_error_timeout">Inactive, check app</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
index 8ee893c14727..359da13a9799 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
@@ -249,7 +249,8 @@ public class RotationButtonController {
}
public void setRotationLockedAtAngle(int rotationSuggestion) {
- RotationPolicy.setRotationLockAtAngle(mContext, true, rotationSuggestion);
+ RotationPolicy.setRotationLockAtAngle(mContext, /* enabled= */ isRotationLocked(),
+ /* rotation= */ rotationSuggestion);
}
public boolean isRotationLocked() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index 061ca4f7d850..67e3400670ba 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -177,6 +177,8 @@ public class KeyguardPINView extends KeyguardPinBasedInputView {
@Override
public void startAppearAnimation() {
+ setAlpha(1f);
+ setTranslationY(0);
if (mAppearAnimator.isRunning()) {
mAppearAnimator.cancel();
}
@@ -213,7 +215,6 @@ public class KeyguardPINView extends KeyguardPinBasedInputView {
/** Animate subviews according to expansion or time. */
private void animate(float progress) {
- setAlpha(progress);
Interpolator standardDecelerate = Interpolators.STANDARD_DECELERATE;
Interpolator legacyDecelerate = Interpolators.LEGACY_DECELERATE;
diff --git a/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt b/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt
index 98ac2c0bd026..0f00a040b094 100644
--- a/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt
+++ b/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt
@@ -17,8 +17,10 @@
package com.android.keyguard.mediator
import android.annotation.BinderThread
+import android.os.Handler
import android.os.Trace
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.unfold.SysUIUnfoldComponent
import com.android.systemui.util.concurrency.PendingTasksContainer
import com.android.systemui.util.kotlin.getOrNull
@@ -33,7 +35,8 @@ import javax.inject.Inject
*/
@SysUISingleton
class ScreenOnCoordinator @Inject constructor(
- unfoldComponent: Optional<SysUIUnfoldComponent>
+ unfoldComponent: Optional<SysUIUnfoldComponent>,
+ @Main private val mainHandler: Handler
) {
private val unfoldLightRevealAnimation = unfoldComponent.map(
@@ -55,7 +58,11 @@ class ScreenOnCoordinator @Inject constructor(
unfoldLightRevealAnimation?.onScreenTurningOn(pendingTasks.registerTask("unfold-reveal"))
foldAodAnimationController?.onScreenTurningOn(pendingTasks.registerTask("fold-to-aod"))
- pendingTasks.onTasksComplete { onDrawn.run() }
+ pendingTasks.onTasksComplete {
+ mainHandler.post {
+ onDrawn.run()
+ }
+ }
Trace.endSection()
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
index 777d10c7acfd..6d54d389a38d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
@@ -182,9 +182,9 @@ public class AccessibilityFloatingMenuController implements
if (mFloatingMenu == null) {
if (mFeatureFlags.isEnabled(A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS)) {
final Display defaultDisplay = mDisplayManager.getDisplay(DEFAULT_DISPLAY);
- mFloatingMenu = new MenuViewLayerController(
- mContext.createWindowContext(defaultDisplay,
- TYPE_NAVIGATION_BAR_PANEL, /* options= */ null), mWindowManager,
+ final Context windowContext = mContext.createWindowContext(defaultDisplay,
+ TYPE_NAVIGATION_BAR_PANEL, /* options= */ null);
+ mFloatingMenu = new MenuViewLayerController(windowContext, mWindowManager,
mAccessibilityManager);
} else {
mFloatingMenu = new AccessibilityFloatingMenu(mContext);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java
index ee048e1a02d3..c2bc1408274f 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java
@@ -19,8 +19,6 @@ package com.android.systemui.accessibility.floatingmenu;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
-import android.content.ComponentCallbacks;
-import android.content.res.Configuration;
import android.view.MotionEvent;
import androidx.annotation.NonNull;
@@ -34,7 +32,7 @@ import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
* Controls the interaction between {@link MagnetizedObject} and
* {@link MagnetizedObject.MagneticTarget}.
*/
-class DismissAnimationController implements ComponentCallbacks {
+class DismissAnimationController {
private static final float COMPLETELY_OPAQUE = 1.0f;
private static final float COMPLETELY_TRANSPARENT = 0.0f;
private static final float CIRCLE_VIEW_DEFAULT_SCALE = 1.0f;
@@ -105,16 +103,6 @@ class DismissAnimationController implements ComponentCallbacks {
mMagnetizedObject.addTarget(magneticTarget);
}
- @Override
- public void onConfigurationChanged(@NonNull Configuration newConfig) {
- updateResources();
- }
-
- @Override
- public void onLowMemory() {
- // Do nothing
- }
-
void showDismissView(boolean show) {
if (show) {
mDismissView.show();
@@ -165,7 +153,7 @@ class DismissAnimationController implements ComponentCallbacks {
}
}
- private void updateResources() {
+ void updateResources() {
final float maxDismissSize = mDismissView.getResources().getDimensionPixelSize(
R.dimen.dismiss_circle_size);
mMinDismissSize = mDismissView.getResources().getDimensionPixelSize(
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipView.java
index 440053450d2f..5ec024ebc917 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipView.java
@@ -21,6 +21,7 @@ import static android.view.View.MeasureSpec.AT_MOST;
import static android.view.View.MeasureSpec.UNSPECIFIED;
import android.annotation.SuppressLint;
+import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -48,7 +49,7 @@ import com.android.systemui.recents.TriangleShape;
* . It's just shown on the left or right of the anchor view.
*/
@SuppressLint("ViewConstructor")
-class MenuEduTooltipView extends FrameLayout {
+class MenuEduTooltipView extends FrameLayout implements ComponentCallbacks {
private int mFontSize;
private int mTextViewMargin;
private int mTextViewPadding;
@@ -73,9 +74,7 @@ class MenuEduTooltipView extends FrameLayout {
}
@Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
-
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
updateResources();
updateMessageView();
updateArrowView();
@@ -83,6 +82,25 @@ class MenuEduTooltipView extends FrameLayout {
updateLocationAndVisibility();
}
+ @Override
+ public void onLowMemory() {
+ // Do nothing.
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ getContext().registerComponentCallbacks(this);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ getContext().unregisterComponentCallbacks(this);
+ }
+
void show(CharSequence message) {
mMessageView.setText(message);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
index 05e1d3f0e126..f79c3d27b2ec 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
@@ -30,13 +30,21 @@ import static com.android.systemui.accessibility.floatingmenu.MenuViewAppearance
import android.annotation.FloatRange;
import android.annotation.IntDef;
+import android.content.ComponentCallbacks;
import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
import android.database.ContentObserver;
+import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
+import android.util.Log;
+import android.view.accessibility.AccessibilityManager;
+
+import androidx.annotation.NonNull;
import com.android.internal.accessibility.dialog.AccessibilityTarget;
import com.android.internal.annotations.VisibleForTesting;
@@ -50,6 +58,9 @@ import java.util.List;
* Stores and observe the settings contents for the menu view.
*/
class MenuInfoRepository {
+ private static final String TAG = "MenuInfoRepository";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG) || Build.IS_DEBUGGABLE;
+
@FloatRange(from = 0.0, to = 1.0)
private static final float DEFAULT_MENU_POSITION_X_PERCENT = 1.0f;
@@ -60,6 +71,10 @@ class MenuInfoRepository {
private static final int DEFAULT_MIGRATION_TOOLTIP_VALUE_PROMPT = MigrationPrompt.DISABLED;
private final Context mContext;
+ private final Configuration mConfiguration;
+ private final AccessibilityManager mAccessibilityManager;
+ private final AccessibilityManager.AccessibilityServicesStateChangeListener
+ mA11yServicesStateChangeListener = manager -> onTargetFeaturesChanged();
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final OnSettingsContentsChanged mSettingsContentsCallback;
private Position mPercentagePosition;
@@ -74,12 +89,12 @@ class MenuInfoRepository {
int ENABLED = 1;
}
- private final ContentObserver mMenuTargetFeaturesContentObserver =
+ @VisibleForTesting
+ final ContentObserver mMenuTargetFeaturesContentObserver =
new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
- mSettingsContentsCallback.onTargetFeaturesChanged(
- getTargets(mContext, ACCESSIBILITY_BUTTON));
+ onTargetFeaturesChanged();
}
};
@@ -102,8 +117,35 @@ class MenuInfoRepository {
}
};
- MenuInfoRepository(Context context, OnSettingsContentsChanged settingsContentsChanged) {
+ @VisibleForTesting
+ final ComponentCallbacks mComponentCallbacks = new ComponentCallbacks() {
+ @Override
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ final int diff = newConfig.diff(mConfiguration);
+
+ if (DEBUG) {
+ Log.d(TAG, "onConfigurationChanged = " + Configuration.configurationDiffToString(
+ diff));
+ }
+
+ if ((diff & ActivityInfo.CONFIG_LOCALE) != 0) {
+ onTargetFeaturesChanged();
+ }
+
+ mConfiguration.setTo(newConfig);
+ }
+
+ @Override
+ public void onLowMemory() {
+ // Do nothing.
+ }
+ };
+
+ MenuInfoRepository(Context context, AccessibilityManager accessibilityManager,
+ OnSettingsContentsChanged settingsContentsChanged) {
mContext = context;
+ mAccessibilityManager = accessibilityManager;
+ mConfiguration = new Configuration(context.getResources().getConfiguration());
mSettingsContentsCallback = settingsContentsChanged;
mPercentagePosition = getStartPosition();
@@ -172,6 +214,11 @@ class MenuInfoRepository {
UserHandle.USER_CURRENT);
}
+ private void onTargetFeaturesChanged() {
+ mSettingsContentsCallback.onTargetFeaturesChanged(
+ getTargets(mContext, ACCESSIBILITY_BUTTON));
+ }
+
private Position getStartPosition() {
final String absolutePositionString = Prefs.getString(mContext,
Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, /* defaultValue= */ null);
@@ -181,7 +228,7 @@ class MenuInfoRepository {
: Position.fromString(absolutePositionString);
}
- void registerContentObservers() {
+ void registerObserversAndCallbacks() {
mContext.getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS),
/* notifyForDescendants */ false, mMenuTargetFeaturesContentObserver,
@@ -202,12 +249,20 @@ class MenuInfoRepository {
Settings.Secure.getUriFor(ACCESSIBILITY_FLOATING_MENU_OPACITY),
/* notifyForDescendants */ false, mMenuFadeOutContentObserver,
UserHandle.USER_CURRENT);
+ mContext.registerComponentCallbacks(mComponentCallbacks);
+
+ mAccessibilityManager.addAccessibilityServicesStateChangeListener(
+ mA11yServicesStateChangeListener);
}
- void unregisterContentObservers() {
+ void unregisterObserversAndCallbacks() {
mContext.getContentResolver().unregisterContentObserver(mMenuTargetFeaturesContentObserver);
mContext.getContentResolver().unregisterContentObserver(mMenuSizeContentObserver);
mContext.getContentResolver().unregisterContentObserver(mMenuFadeOutContentObserver);
+ mContext.unregisterComponentCallbacks(mComponentCallbacks);
+
+ mAccessibilityManager.removeAccessibilityServicesStateChangeListener(
+ mA11yServicesStateChangeListener);
}
interface OnSettingsContentsChanged {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
index 9875ad06f1ed..3e2b06b39bad 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
@@ -20,6 +20,7 @@ import static android.util.TypedValue.COMPLEX_UNIT_PX;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.annotation.IntDef;
+import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
@@ -33,6 +34,8 @@ import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
+import androidx.annotation.NonNull;
+
import com.android.settingslib.Utils;
import com.android.systemui.R;
@@ -44,7 +47,7 @@ import java.lang.annotation.RetentionPolicy;
* the {@link MenuView}.
*/
class MenuMessageView extends LinearLayout implements
- ViewTreeObserver.OnComputeInternalInsetsListener {
+ ViewTreeObserver.OnComputeInternalInsetsListener, ComponentCallbacks {
private final TextView mTextView;
private final Button mUndoButton;
@@ -61,6 +64,7 @@ class MenuMessageView extends LinearLayout implements
MenuMessageView(Context context) {
super(context);
+ setLayoutDirection(LAYOUT_DIRECTION_LOCALE);
setVisibility(GONE);
mTextView = new TextView(context);
@@ -72,13 +76,16 @@ class MenuMessageView extends LinearLayout implements
}
@Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
-
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
updateResources();
}
@Override
+ public void onLowMemory() {
+ // Do nothing.
+ }
+
+ @Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
@@ -92,6 +99,7 @@ class MenuMessageView extends LinearLayout implements
updateResources();
+ getContext().registerComponentCallbacks(this);
getViewTreeObserver().addOnComputeInternalInsetsListener(this);
}
@@ -99,6 +107,7 @@ class MenuMessageView extends LinearLayout implements
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
+ getContext().unregisterComponentCallbacks(this);
getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
index 986aa51ecce1..28269d943ee3 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
@@ -19,6 +19,7 @@ package com.android.systemui.accessibility.floatingmenu;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.annotation.SuppressLint;
+import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.PointF;
@@ -46,7 +47,7 @@ import java.util.List;
*/
@SuppressLint("ViewConstructor")
class MenuView extends FrameLayout implements
- ViewTreeObserver.OnComputeInternalInsetsListener {
+ ViewTreeObserver.OnComputeInternalInsetsListener, ComponentCallbacks {
private static final int INDEX_MENU_ITEM = 0;
private final List<AccessibilityTarget> mTargetFeatures = new ArrayList<>();
private final AccessibilityTargetAdapter mAdapter;
@@ -106,14 +107,31 @@ class MenuView extends FrameLayout implements
}
@Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
-
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
loadLayoutResources();
mTargetFeaturesView.setOverScrollMode(mMenuViewAppearance.getMenuScrollMode());
}
+ @Override
+ public void onLowMemory() {
+ // Do nothing.
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ getContext().registerComponentCallbacks(this);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ getContext().unregisterComponentCallbacks(this);
+ }
+
void setOnTargetFeaturesChangeListener(OnTargetFeaturesChangeListener listener) {
mFeaturesChangeListener = listener;
}
@@ -299,7 +317,7 @@ class MenuView extends FrameLayout implements
mMenuViewModel.getSizeTypeData().observeForever(mSizeTypeObserver);
mMenuViewModel.getMoveToTuckedData().observeForever(mMoveToTuckedObserver);
setVisibility(VISIBLE);
- mMenuViewModel.registerContentObservers();
+ mMenuViewModel.registerObserversAndCallbacks();
getViewTreeObserver().addOnComputeInternalInsetsListener(this);
getViewTreeObserver().addOnDrawListener(mSystemGestureExcludeUpdater);
}
@@ -312,7 +330,7 @@ class MenuView extends FrameLayout implements
mMenuViewModel.getTargetFeaturesData().removeObserver(mTargetFeaturesObserver);
mMenuViewModel.getSizeTypeData().removeObserver(mSizeTypeObserver);
mMenuViewModel.getMoveToTuckedData().removeObserver(mMoveToTuckedObserver);
- mMenuViewModel.unregisterContentObservers();
+ mMenuViewModel.unregisterObserversAndCallbacks();
getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
getViewTreeObserver().removeOnDrawListener(mSystemGestureExcludeUpdater);
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index 6f5b39cc7d56..15a8d09e5fd7 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -25,20 +25,22 @@ import static com.android.internal.accessibility.common.ShortcutConstants.Access
import static com.android.internal.accessibility.util.AccessibilityUtils.getAccessibilityServiceFragmentType;
import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState;
import static com.android.systemui.accessibility.floatingmenu.MenuMessageView.Index;
+import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.IntDef;
import android.annotation.StringDef;
import android.annotation.SuppressLint;
+import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.provider.Settings;
-import android.util.PluralsMessageFormatter;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
@@ -61,9 +63,7 @@ import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
import java.util.Optional;
/**
@@ -74,7 +74,7 @@ import java.util.Optional;
*/
@SuppressLint("ViewConstructor")
class MenuViewLayer extends FrameLayout implements
- ViewTreeObserver.OnComputeInternalInsetsListener, View.OnClickListener {
+ ViewTreeObserver.OnComputeInternalInsetsListener, View.OnClickListener, ComponentCallbacks {
private static final int SHOW_MESSAGE_DELAY_MS = 3000;
private final WindowManager mWindowManager;
@@ -137,8 +137,8 @@ class MenuViewLayer extends FrameLayout implements
AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
serviceInfoList.forEach(info -> {
if (getAccessibilityServiceFragmentType(info) == INVISIBLE_TOGGLE) {
- setAccessibilityServiceState(mContext, info.getComponentName(), /* enabled= */
- false);
+ setAccessibilityServiceState(getContext(),
+ info.getComponentName(), /* enabled= */ false);
}
});
@@ -150,11 +150,14 @@ class MenuViewLayer extends FrameLayout implements
AccessibilityManager accessibilityManager, IAccessibilityFloatingMenu floatingMenu) {
super(context);
+ // Simplifies the translation positioning and animations
+ setLayoutDirection(LAYOUT_DIRECTION_LTR);
+
mWindowManager = windowManager;
mAccessibilityManager = accessibilityManager;
mFloatingMenu = floatingMenu;
- mMenuViewModel = new MenuViewModel(context);
+ mMenuViewModel = new MenuViewModel(context, accessibilityManager);
mMenuViewAppearance = new MenuViewAppearance(context, windowManager);
mMenuView = new MenuView(context, mMenuViewModel, mMenuViewAppearance);
mMenuAnimationController = mMenuView.getMenuAnimationController();
@@ -209,20 +212,30 @@ class MenuViewLayer extends FrameLayout implements
}
@Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
mDismissView.updateResources();
+ mDismissAnimationController.updateResources();
+ }
+
+ @Override
+ public void onLowMemory() {
+ // Do nothing.
}
private String getMessageText(List<AccessibilityTarget> newTargetFeatures) {
Preconditions.checkArgument(newTargetFeatures.size() > 0,
"The list should at least have one feature.");
- final Map<String, Object> arguments = new HashMap<>();
- arguments.put("count", newTargetFeatures.size());
- arguments.put("label", newTargetFeatures.get(0).getLabel());
- return PluralsMessageFormatter.format(getResources(), arguments,
- R.string.accessibility_floating_button_undo_message_text);
+ final int featuresSize = newTargetFeatures.size();
+ final Resources resources = getResources();
+ if (featuresSize == 1) {
+ return resources.getString(
+ R.string.accessibility_floating_button_undo_message_label_text,
+ newTargetFeatures.get(0).getLabel());
+ }
+
+ return icuMessageFormat(resources,
+ R.string.accessibility_floating_button_undo_message_number_text, featuresSize);
}
@Override
@@ -246,7 +259,7 @@ class MenuViewLayer extends FrameLayout implements
mMenuViewModel.getMigrationTooltipVisibilityData().observeForever(
mMigrationTooltipObserver);
mMessageView.setUndoListener(view -> undo());
- mContext.registerComponentCallbacks(mDismissAnimationController);
+ getContext().registerComponentCallbacks(this);
}
@Override
@@ -261,7 +274,7 @@ class MenuViewLayer extends FrameLayout implements
mMenuViewModel.getMigrationTooltipVisibilityData().removeObserver(
mMigrationTooltipObserver);
mHandler.removeCallbacksAndMessages(/* token= */ null);
- mContext.unregisterComponentCallbacks(mDismissAnimationController);
+ getContext().unregisterComponentCallbacks(this);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java
index 5fea3b0ba2f9..eec84672f17c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java
@@ -17,6 +17,7 @@
package com.android.systemui.accessibility.floatingmenu;
import android.content.Context;
+import android.view.accessibility.AccessibilityManager;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
@@ -41,8 +42,9 @@ class MenuViewModel implements MenuInfoRepository.OnSettingsContentsChanged {
private final MutableLiveData<Position> mPercentagePositionData = new MutableLiveData<>();
private final MenuInfoRepository mInfoRepository;
- MenuViewModel(Context context) {
- mInfoRepository = new MenuInfoRepository(context, /* settingsContentsChanged= */ this);
+ MenuViewModel(Context context, AccessibilityManager accessibilityManager) {
+ mInfoRepository = new MenuInfoRepository(context,
+ accessibilityManager, /* settingsContentsChanged= */ this);
}
@Override
@@ -111,11 +113,11 @@ class MenuViewModel implements MenuInfoRepository.OnSettingsContentsChanged {
return mTargetFeaturesData;
}
- void registerContentObservers() {
- mInfoRepository.registerContentObservers();
+ void registerObserversAndCallbacks() {
+ mInfoRepository.registerObserversAndCallbacks();
}
- void unregisterContentObservers() {
- mInfoRepository.unregisterContentObservers();
+ void unregisterObserversAndCallbacks() {
+ mInfoRepository.unregisterObserversAndCallbacks();
}
} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 76c1158373b7..96bfa4323f95 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -643,6 +643,7 @@ public class FrameworkServicesModule {
@Provides
@Singleton
+ @Nullable
static BluetoothAdapter provideBluetoothAdapter(BluetoothManager bluetoothManager) {
return bluetoothManager.getAdapter();
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 44fd3534973d..764a3d0a566d 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -419,7 +419,13 @@ object Flags {
unreleasedFlag(1204, "persist.wm.debug.predictive_back_sysui_enable", teamfood = true)
// TODO(b/255697805): Tracking Bug
- @JvmField val TRACKPAD_GESTURE_BACK = unreleasedFlag(1205, "trackpad_gesture_back", teamfood = false)
+ @JvmField
+ val TRACKPAD_GESTURE_BACK = unreleasedFlag(1205, "trackpad_gesture_back", teamfood = false)
+
+ // TODO(b/263826204): Tracking Bug
+ @JvmField
+ val WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM =
+ unreleasedFlag(1206, "persist.wm.debug.predictive_back_bouncer_anim", teamfood = true)
// 1300 - screenshots
// TODO(b/254512719): Tracking Bug
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index 2a3a33eff274..2cf5fb98d07e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -77,6 +77,7 @@ constructor(
/** Runnable to show the primary bouncer. */
val showRunnable = Runnable {
+ repository.setPrimaryVisible(true)
repository.setPrimaryShow(
KeyguardBouncerModel(
promptReason = repository.bouncerPromptReason ?: 0,
@@ -85,7 +86,6 @@ constructor(
)
)
repository.setPrimaryShowingSoon(false)
- repository.setPrimaryVisible(true)
primaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.VISIBLE)
}
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index 90fc1d7ed49e..3587c4d8cc77 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -71,6 +71,7 @@ import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.NotificationChannels;
+import com.android.systemui.util.settings.GlobalSettings;
import com.android.systemui.volume.Events;
import java.io.PrintWriter;
@@ -175,6 +176,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
private ActivityStarter mActivityStarter;
private final BroadcastSender mBroadcastSender;
private final UiEventLogger mUiEventLogger;
+ private GlobalSettings mGlobalSettings;
private final Lazy<BatteryController> mBatteryControllerLazy;
private final DialogLaunchAnimator mDialogLaunchAnimator;
@@ -184,7 +186,8 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
@Inject
public PowerNotificationWarnings(Context context, ActivityStarter activityStarter,
BroadcastSender broadcastSender, Lazy<BatteryController> batteryControllerLazy,
- DialogLaunchAnimator dialogLaunchAnimator, UiEventLogger uiEventLogger) {
+ DialogLaunchAnimator dialogLaunchAnimator, UiEventLogger uiEventLogger,
+ GlobalSettings globalSettings) {
mContext = context;
mNoMan = mContext.getSystemService(NotificationManager.class);
mPowerMan = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
@@ -196,6 +199,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
mDialogLaunchAnimator = dialogLaunchAnimator;
mUseSevereDialog = mContext.getResources().getBoolean(R.bool.config_severe_battery_dialog);
mUiEventLogger = uiEventLogger;
+ mGlobalSettings = globalSettings;
}
@Override
@@ -281,6 +285,9 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
}
protected void showWarningNotification() {
+ if (mGlobalSettings.getInt(Global.LOW_POWER_MODE_REMINDER_ENABLED, 1) == 0) {
+ return;
+ }
if (showSevereLowBatteryDialog()) {
mBroadcastSender.sendBroadcast(new Intent(ACTION_ENABLE_SEVERE_BATTERY_DIALOG)
.setPackage(mContext.getPackageName())
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 893574a59d94..da18b5734e81 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -19,7 +19,8 @@ import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
import static com.android.systemui.media.dagger.MediaModule.QS_PANEL;
import static com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL;
import static com.android.systemui.statusbar.disableflags.DisableFlagsLogger.DisableState;
-
+import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
+import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.res.Configuration;
@@ -124,7 +125,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
* we're on keyguard but use {@link #isKeyguardState()} instead since that is more accurate
* during state transitions which often call into us.
*/
- private int mState;
+ private int mStatusBarState = -1;
private QSContainerImplController mQSContainerImplController;
private int[] mTmpLocation = new int[2];
private int mLastViewHeight;
@@ -457,7 +458,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
private boolean isKeyguardState() {
// We want the freshest state here since otherwise we'll have some weirdness if earlier
// listeners trigger updates
- return mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
+ return mStatusBarStateController.getCurrentOrUpcomingState() == KEYGUARD;
}
private void updateShowCollapsedOnKeyguard() {
@@ -672,8 +673,8 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
mQSAnimator.setPosition(expansion);
}
if (!mInSplitShade
- || mStatusBarStateController.getState() == StatusBarState.KEYGUARD
- || mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) {
+ || mStatusBarStateController.getState() == KEYGUARD
+ || mStatusBarStateController.getState() == SHADE_LOCKED) {
// At beginning, state is 0 and will apply wrong squishiness to MediaHost in lockscreen
// and media player expect no change by squishiness in lock screen shade. Don't bother
// squishing mQsMediaHost when not in split shade to prevent problems with stale state.
@@ -703,8 +704,8 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
// Large screens in landscape.
// Need to check upcoming state as for unlocked -> AOD transition current state is
// not updated yet, but we're transitioning and UI should already follow KEYGUARD state
- if (mTransitioningToFullShade || mStatusBarStateController.getCurrentOrUpcomingState()
- == StatusBarState.KEYGUARD) {
+ if (mTransitioningToFullShade
+ || mStatusBarStateController.getCurrentOrUpcomingState() == KEYGUARD) {
// Always use "mFullShadeProgress" on keyguard, because
// "panelExpansionFractions" is always 1 on keyguard split shade.
return mLockscreenToShadeProgress;
@@ -757,8 +758,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
}
private boolean headerWillBeAnimating() {
- return mState == StatusBarState.KEYGUARD && mShowCollapsedOnKeyguard
- && !isKeyguardState();
+ return mStatusBarState == KEYGUARD && mShowCollapsedOnKeyguard && !isKeyguardState();
}
@Override
@@ -891,9 +891,23 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
};
@Override
+ public void onUpcomingStateChanged(int upcomingState) {
+ if (upcomingState == KEYGUARD) {
+ // refresh state of QS as soon as possible - while it's still upcoming - so in case of
+ // transition to KEYGUARD (e.g. from unlocked to AOD) all objects are aware they should
+ // already behave like on keyguard. Otherwise we might be doing extra work,
+ // e.g. QSAnimator making QS visible and then quickly invisible
+ onStateChanged(upcomingState);
+ }
+ }
+
+ @Override
public void onStateChanged(int newState) {
- mState = newState;
- setKeyguardShowing(newState == StatusBarState.KEYGUARD);
+ if (newState == mStatusBarState) {
+ return;
+ }
+ mStatusBarState = newState;
+ setKeyguardShowing(newState == KEYGUARD);
updateShowCollapsedOnKeyguard();
}
@@ -921,7 +935,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
indentingPw.println("mTemp: " + Arrays.toString(mLocationTemp));
indentingPw.println("mShowCollapsedOnKeyguard: " + mShowCollapsedOnKeyguard);
indentingPw.println("mLastKeyguardAndExpanded: " + mLastKeyguardAndExpanded);
- indentingPw.println("mState: " + StatusBarState.toString(mState));
+ indentingPw.println("mStatusBarState: " + StatusBarState.toString(mStatusBarState));
indentingPw.println("mTmpLocation: " + Arrays.toString(mTmpLocation));
indentingPw.println("mLastViewHeight: " + mLastViewHeight);
indentingPw.println("mLastHeaderTranslation: " + mLastHeaderTranslation);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
index 4d914fe0adef..15fed3244d97 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
@@ -49,9 +49,9 @@ class StatusBarPipelineFlags @Inject constructor(private val featureFlags: Featu
featureFlags.isEnabled(Flags.NEW_STATUS_BAR_WIFI_ICON_BACKEND) || useNewWifiIcon()
/**
- * Returns true if we should apply some coloring to the wifi icon that was rendered with the new
+ * Returns true if we should apply some coloring to the icons that were rendered with the new
* pipeline to help with debugging.
*/
- fun useWifiDebugColoring(): Boolean =
+ fun useDebugColoring(): Boolean =
featureFlags.isEnabled(Flags.NEW_STATUS_BAR_ICONS_DEBUG_COLORING)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 0d01715715c0..0993ab3701f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.dagger
+import android.net.wifi.WifiManager
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.table.TableLogBuffer
@@ -35,8 +36,11 @@ import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxyImpl
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositorySwitcher
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.DisabledWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
import dagger.Binds
@@ -78,9 +82,23 @@ abstract class StatusBarPipelineModule {
@ClassKey(MobileUiAdapter::class)
abstract fun bindFeature(impl: MobileUiAdapter): CoreStartable
- @Module
companion object {
- @JvmStatic
+ @Provides
+ @SysUISingleton
+ fun provideRealWifiRepository(
+ wifiManager: WifiManager?,
+ disabledWifiRepository: DisabledWifiRepository,
+ wifiRepositoryImplFactory: WifiRepositoryImpl.Factory,
+ ): RealWifiRepository {
+ // If we have a null [WifiManager], then the wifi repository should be permanently
+ // disabled.
+ return if (wifiManager == null) {
+ disabledWifiRepository
+ } else {
+ wifiRepositoryImplFactory.create(wifiManager)
+ }
+ }
+
@Provides
@SysUISingleton
@WifiTableLog
@@ -88,7 +106,6 @@ abstract class StatusBarPipelineModule {
return factory.create("WifiTableLog", 100)
}
- @JvmStatic
@Provides
@SysUISingleton
@AirplaneTableLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt
index dd93541d7c8f..59603874efde 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.pipeline.mobile.data.model
import android.telephony.Annotation.NetworkType
-import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
/**
@@ -26,21 +25,17 @@ import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
* methods on [MobileMappingsProxy] to generate an icon lookup key.
*/
sealed interface ResolvedNetworkType {
- @NetworkType val type: Int
val lookupKey: String
object UnknownNetworkType : ResolvedNetworkType {
- override val type: Int = NETWORK_TYPE_UNKNOWN
override val lookupKey: String = "unknown"
}
data class DefaultNetworkType(
- @NetworkType override val type: Int,
override val lookupKey: String,
) : ResolvedNetworkType
data class OverrideNetworkType(
- @NetworkType override val type: Int,
override val lookupKey: String,
) : ResolvedNetworkType
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index 40e9ba1a46c7..d04996b4d6ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
import android.telephony.SubscriptionInfo
-import android.telephony.SubscriptionManager
import android.telephony.TelephonyCallback
import android.telephony.TelephonyManager
import com.android.systemui.log.table.TableLogBuffer
@@ -52,13 +51,12 @@ interface MobileConnectionRepository {
* listener + model.
*/
val connectionInfo: Flow<MobileConnectionModel>
+
+ /** The total number of levels. Used with [SignalDrawable]. */
+ val numberOfLevels: StateFlow<Int>
+
/** Observable tracking [TelephonyManager.isDataConnectionAllowed] */
val dataEnabled: StateFlow<Boolean>
- /**
- * True if this connection represents the default subscription per
- * [SubscriptionManager.getDefaultDataSubscriptionId]
- */
- val isDefaultDataSubscription: StateFlow<Boolean>
/**
* See [TelephonyManager.getCdmaEnhancedRoamingIndicatorDisplayNumber]. This bit only matters if
@@ -70,4 +68,9 @@ interface MobileConnectionRepository {
/** The service provider name for this network connection, or the default name */
val networkName: StateFlow<NetworkNameModel>
+
+ companion object {
+ /** The default number of levels to use for [numberOfLevels]. */
+ const val DEFAULT_NUM_LEVELS = 4
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
index 498c0b93fce8..97b4c2cadbe5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
@@ -18,7 +18,6 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository
import android.provider.Settings
import android.telephony.CarrierConfigManager
-import android.telephony.SubscriptionManager
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.MobileMappings.Config
@@ -38,9 +37,6 @@ interface MobileConnectionsRepository {
/** Observable for the subscriptionId of the current mobile data connection */
val activeMobileDataSubscriptionId: StateFlow<Int>
- /** Tracks [SubscriptionManager.getDefaultDataSubscriptionId] */
- val defaultDataSubId: StateFlow<Int>
-
/** The current connectivity status for the default mobile network connection */
val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
index db9d24ff7aba..0c8593d60cf5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
@@ -139,11 +139,6 @@ constructor(
override val defaultMobileIconGroup: Flow<SignalIcon.MobileIconGroup> =
activeRepo.flatMapLatest { it.defaultMobileIconGroup }
- override val defaultDataSubId: StateFlow<Int> =
- activeRepo
- .flatMapLatest { it.defaultDataSubId }
- .stateIn(scope, SharingStarted.WhileSubscribed(), realRepository.defaultDataSubId.value)
-
override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> =
activeRepo
.flatMapLatest { it.defaultMobileNetworkConnectivity }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
index 0b5f9d5ae59e..0e164e7ee859 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
@@ -34,6 +34,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetwork
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.Mobile
@@ -139,14 +140,6 @@ constructor(
private fun <K, V> Map<K, V>.reverse() = entries.associateBy({ it.value }) { it.key }
- // TODO(b/261029387): add a command for this value
- override val defaultDataSubId =
- activeMobileDataSubscriptionId.stateIn(
- scope,
- SharingStarted.WhileSubscribed(),
- INVALID_SUBSCRIPTION_ID
- )
-
// TODO(b/261029387): not yet supported
override val defaultMobileNetworkConnectivity = MutableStateFlow(MobileConnectivityModel())
@@ -199,7 +192,6 @@ constructor(
val connection = getRepoForSubId(subId)
// This is always true here, because we split out disabled states at the data-source level
connection.dataEnabled.value = true
- connection.isDefaultDataSubscription.value = state.dataType != null
connection.networkName.value = NetworkNameModel.Derived(state.name)
connection.cdmaRoaming.value = state.roaming
@@ -261,15 +253,13 @@ constructor(
private fun SignalIcon.MobileIconGroup?.toResolvedNetworkType(): ResolvedNetworkType {
val key = mobileMappingsReverseLookup.value[this] ?: "dis"
- return DefaultNetworkType(DEMO_NET_TYPE, key)
+ return DefaultNetworkType(key)
}
companion object {
private const val TAG = "DemoMobileConnectionsRepo"
private const val DEFAULT_SUB_ID = 1
-
- private const val DEMO_NET_TYPE = 1234
}
}
@@ -279,9 +269,9 @@ class DemoMobileConnectionRepository(
) : MobileConnectionRepository {
override val connectionInfo = MutableStateFlow(MobileConnectionModel())
- override val dataEnabled = MutableStateFlow(true)
+ override val numberOfLevels = MutableStateFlow(DEFAULT_NUM_LEVELS)
- override val isDefaultDataSubscription = MutableStateFlow(true)
+ override val dataEnabled = MutableStateFlow(true)
override val cdmaRoaming = MutableStateFlow(false)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index 5cfff82253c5..0fa0fea0bebf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -48,6 +48,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetwork
import com.android.systemui.statusbar.pipeline.mobile.data.model.toDataConnectionType
import com.android.systemui.statusbar.pipeline.mobile.data.model.toNetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
@@ -63,6 +64,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
@@ -78,7 +80,6 @@ class MobileConnectionRepositoryImpl(
private val telephonyManager: TelephonyManager,
private val globalSettings: GlobalSettings,
broadcastDispatcher: BroadcastDispatcher,
- defaultDataSubId: StateFlow<Int>,
globalMobileDataSettingChangedEvent: Flow<Unit>,
mobileMappingsProxy: MobileMappingsProxy,
bgDispatcher: CoroutineDispatcher,
@@ -185,14 +186,12 @@ class MobileConnectionRepositoryImpl(
OVERRIDE_NETWORK_TYPE_NONE
) {
DefaultNetworkType(
- telephonyDisplayInfo.networkType,
mobileMappingsProxy.toIconKey(
telephonyDisplayInfo.networkType
)
)
} else {
OverrideNetworkType(
- telephonyDisplayInfo.overrideNetworkType,
mobileMappingsProxy.toIconKeyOverride(
telephonyDisplayInfo.overrideNetworkType
)
@@ -214,6 +213,12 @@ class MobileConnectionRepositoryImpl(
.stateIn(scope, SharingStarted.WhileSubscribed(), state)
}
+ // This will become variable based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL]
+ // once it's wired up inside of [CarrierConfigTracker].
+ override val numberOfLevels: StateFlow<Int> =
+ flowOf(DEFAULT_NUM_LEVELS)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), DEFAULT_NUM_LEVELS)
+
/** Produces whenever the mobile data setting changes for this subId */
private val localMobileDataSettingChangedEvent: Flow<Unit> = conflatedCallbackFlow {
val observer =
@@ -284,20 +289,6 @@ class MobileConnectionRepositoryImpl(
private fun dataConnectionAllowed(): Boolean = telephonyManager.isDataConnectionAllowed
- override val isDefaultDataSubscription: StateFlow<Boolean> = run {
- val initialValue = defaultDataSubId.value == subId
- defaultDataSubId
- .mapLatest { it == subId }
- .distinctUntilChanged()
- .logDiffsForTable(
- mobileLogger,
- columnPrefix = "",
- columnName = "isDefaultDataSub",
- initialValue = initialValue,
- )
- .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue)
- }
-
class Factory
@Inject
constructor(
@@ -315,7 +306,6 @@ class MobileConnectionRepositoryImpl(
subId: Int,
defaultNetworkName: NetworkNameModel,
networkNameSeparator: String,
- defaultDataSubId: StateFlow<Int>,
globalMobileDataSettingChangedEvent: Flow<Unit>,
): MobileConnectionRepository {
val mobileLogger = logFactory.create(tableBufferLogName(subId), 100)
@@ -328,7 +318,6 @@ class MobileConnectionRepositoryImpl(
telephonyManager.createForSubscriptionId(subId),
globalSettings,
broadcastDispatcher,
- defaultDataSubId,
globalMobileDataSettingChangedEvent,
mobileMappingsProxy,
bgDispatcher,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index d407abeb2315..c88c70064238 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -35,7 +35,6 @@ import android.telephony.TelephonyCallback
import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
import android.telephony.TelephonyManager
import androidx.annotation.VisibleForTesting
-import com.android.internal.telephony.PhoneConstants
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.MobileMappings.Config
import com.android.systemui.R
@@ -60,7 +59,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -142,24 +140,10 @@ constructor(
.logInputChange(logger, "onActiveDataSubscriptionIdChanged")
.stateIn(scope, started = SharingStarted.WhileSubscribed(), INVALID_SUBSCRIPTION_ID)
- private val defaultDataSubIdChangeEvent: MutableSharedFlow<Unit> =
- MutableSharedFlow(extraBufferCapacity = 1)
-
- override val defaultDataSubId: StateFlow<Int> =
+ private val defaultDataSubIdChangedEvent =
broadcastDispatcher
- .broadcastFlow(
- IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
- ) { intent, _ ->
- intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, INVALID_SUBSCRIPTION_ID)
- }
- .distinctUntilChanged()
+ .broadcastFlow(IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED))
.logInputChange(logger, "ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED")
- .onEach { defaultDataSubIdChangeEvent.tryEmit(Unit) }
- .stateIn(
- scope,
- SharingStarted.WhileSubscribed(),
- SubscriptionManager.getDefaultDataSubscriptionId()
- )
private val carrierConfigChangedEvent =
broadcastDispatcher
@@ -167,7 +151,7 @@ constructor(
.logInputChange(logger, "ACTION_CARRIER_CONFIG_CHANGED")
override val defaultDataSubRatConfig: StateFlow<Config> =
- merge(defaultDataSubIdChangeEvent, carrierConfigChangedEvent)
+ merge(defaultDataSubIdChangedEvent, carrierConfigChangedEvent)
.mapLatest { Config.readConfig(context) }
.distinctUntilChanged()
.logInputChange(logger, "defaultDataSubRatConfig")
@@ -272,7 +256,6 @@ constructor(
subId,
defaultNetworkName,
networkNameSeparator,
- defaultDataSubId,
globalMobileDataSettingChangedEvent,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index 31ac7a16a940..9427c6b9fece 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -23,12 +23,11 @@ import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Connected
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.util.CarrierConfigTracker
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
@@ -171,11 +170,12 @@ class MobileIconInteractorImpl(
}
.stateIn(scope, SharingStarted.WhileSubscribed(), 0)
- /**
- * This will become variable based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL]
- * once it's wired up inside of [CarrierConfigTracker]
- */
- override val numberOfLevels: StateFlow<Int> = MutableStateFlow(4)
+ override val numberOfLevels: StateFlow<Int> =
+ connectionRepository.numberOfLevels.stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ connectionRepository.numberOfLevels.value,
+ )
override val isDataConnected: StateFlow<Boolean> =
connectionInfo
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index ab442b5ab4de..3e81c7c7cefd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.binder
import android.content.res.ColorStateList
import android.view.View
import android.view.View.GONE
+import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import android.view.ViewGroup
import android.widget.ImageView
@@ -30,7 +31,13 @@ import com.android.settingslib.graph.SignalDrawable
import com.android.systemui.R
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
+import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
+import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
@@ -40,7 +47,8 @@ object MobileIconBinder {
fun bind(
view: ViewGroup,
viewModel: LocationBasedMobileViewModel,
- ) {
+ ): ModernStatusBarViewBinding {
+ val mobileGroupView = view.requireViewById<ViewGroup>(R.id.mobile_group)
val activityContainer = view.requireViewById<View>(R.id.inout_container)
val activityIn = view.requireViewById<ImageView>(R.id.mobile_in)
val activityOut = view.requireViewById<ImageView>(R.id.mobile_out)
@@ -49,12 +57,39 @@ object MobileIconBinder {
val mobileDrawable = SignalDrawable(view.context).also { iconView.setImageDrawable(it) }
val roamingView = view.requireViewById<ImageView>(R.id.mobile_roaming)
val roamingSpace = view.requireViewById<Space>(R.id.mobile_roaming_space)
+ val dotView = view.requireViewById<StatusBarIconView>(R.id.status_bar_dot)
view.isVisible = true
iconView.isVisible = true
+ // TODO(b/238425913): We should log this visibility state.
+ @StatusBarIconView.VisibleState
+ val visibilityState: MutableStateFlow<Int> = MutableStateFlow(STATE_HIDDEN)
+
+ val iconTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor)
+ val decorTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor)
+
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ visibilityState.collect { state ->
+ when (state) {
+ STATE_ICON -> {
+ mobileGroupView.visibility = VISIBLE
+ dotView.visibility = GONE
+ }
+ STATE_DOT -> {
+ mobileGroupView.visibility = INVISIBLE
+ dotView.visibility = VISIBLE
+ }
+ STATE_HIDDEN -> {
+ mobileGroupView.visibility = INVISIBLE
+ dotView.visibility = INVISIBLE
+ }
+ }
+ }
+ }
+
// Set the icon for the triangle
launch {
viewModel.iconId.distinctUntilChanged().collect { iconId ->
@@ -89,15 +124,43 @@ object MobileIconBinder {
// Set the tint
launch {
- viewModel.tint.collect { tint ->
+ iconTint.collect { tint ->
val tintList = ColorStateList.valueOf(tint)
iconView.imageTintList = tintList
networkTypeView.imageTintList = tintList
roamingView.imageTintList = tintList
activityIn.imageTintList = tintList
activityOut.imageTintList = tintList
+ dotView.setDecorColor(tint)
}
}
+
+ launch { decorTint.collect { tint -> dotView.setDecorColor(tint) } }
+ }
+ }
+
+ return object : ModernStatusBarViewBinding {
+ override fun getShouldIconBeVisible(): Boolean {
+ // If this view model exists, then the icon should be visible.
+ return true
+ }
+
+ override fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int) {
+ visibilityState.value = state
+ }
+
+ override fun onIconTintChanged(newTint: Int) {
+ if (viewModel.useDebugColoring) {
+ return
+ }
+ iconTint.value = newTint
+ }
+
+ override fun onDecorTintChanged(newTint: Int) {
+ if (viewModel.useDebugColoring) {
+ return
+ }
+ decorTint.value = newTint
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
index e86fee24fe4d..ed9a1884a7b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
@@ -17,50 +17,20 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.view
import android.content.Context
-import android.graphics.Rect
import android.util.AttributeSet
import android.view.LayoutInflater
import com.android.systemui.R
-import com.android.systemui.statusbar.BaseStatusBarFrameLayout
-import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconBinder
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
-import java.util.ArrayList
+import com.android.systemui.statusbar.pipeline.shared.ui.view.ModernStatusBarView
class ModernStatusBarMobileView(
context: Context,
attrs: AttributeSet?,
-) : BaseStatusBarFrameLayout(context, attrs) {
+) : ModernStatusBarView(context, attrs) {
var subId: Int = -1
- private lateinit var slot: String
- override fun getSlot() = slot
-
- override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) {
- // TODO
- }
-
- override fun setStaticDrawableColor(color: Int) {
- // TODO
- }
-
- override fun setDecorColor(color: Int) {
- // TODO
- }
-
- override fun setVisibleState(state: Int, animate: Boolean) {
- // TODO
- }
-
- override fun getVisibleState(): Int {
- return STATE_ICON
- }
-
- override fun isIconVisible(): Boolean {
- return true
- }
-
companion object {
/**
@@ -77,9 +47,8 @@ class ModernStatusBarMobileView(
.inflate(R.layout.status_bar_mobile_signal_group_new, null)
as ModernStatusBarMobileView)
.also {
- it.slot = slot
it.subId = viewModel.subscriptionId
- MobileIconBinder.bind(it, viewModel)
+ it.initView(slot) { MobileIconBinder.bind(it, viewModel) }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt
index b0dc41f45488..24cd9304f8dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt
@@ -18,11 +18,7 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
import android.graphics.Color
import com.android.systemui.statusbar.phone.StatusBarLocation
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flowOf
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
/**
* A view model for an individual mobile icon that embeds the notion of a [StatusBarLocation]. This
@@ -33,50 +29,51 @@ import kotlinx.coroutines.flow.flowOf
*/
abstract class LocationBasedMobileViewModel(
val commonImpl: MobileIconViewModelCommon,
- val logger: ConnectivityPipelineLogger,
+ statusBarPipelineFlags: StatusBarPipelineFlags,
+ debugTint: Int,
) : MobileIconViewModelCommon by commonImpl {
- abstract val tint: Flow<Int>
+ val useDebugColoring: Boolean = statusBarPipelineFlags.useDebugColoring()
+
+ val defaultColor: Int =
+ if (useDebugColoring) {
+ debugTint
+ } else {
+ Color.WHITE
+ }
companion object {
fun viewModelForLocation(
commonImpl: MobileIconViewModelCommon,
- logger: ConnectivityPipelineLogger,
+ statusBarPipelineFlags: StatusBarPipelineFlags,
loc: StatusBarLocation,
): LocationBasedMobileViewModel =
when (loc) {
- StatusBarLocation.HOME -> HomeMobileIconViewModel(commonImpl, logger)
- StatusBarLocation.KEYGUARD -> KeyguardMobileIconViewModel(commonImpl, logger)
- StatusBarLocation.QS -> QsMobileIconViewModel(commonImpl, logger)
+ StatusBarLocation.HOME ->
+ HomeMobileIconViewModel(commonImpl, statusBarPipelineFlags)
+ StatusBarLocation.KEYGUARD ->
+ KeyguardMobileIconViewModel(commonImpl, statusBarPipelineFlags)
+ StatusBarLocation.QS -> QsMobileIconViewModel(commonImpl, statusBarPipelineFlags)
}
}
}
class HomeMobileIconViewModel(
commonImpl: MobileIconViewModelCommon,
- logger: ConnectivityPipelineLogger,
-) : MobileIconViewModelCommon, LocationBasedMobileViewModel(commonImpl, logger) {
- override val tint: Flow<Int> =
- flowOf(Color.CYAN)
- .distinctUntilChanged()
- .logOutputChange(logger, "HOME tint(${commonImpl.subscriptionId})")
-}
+ statusBarPipelineFlags: StatusBarPipelineFlags,
+) :
+ MobileIconViewModelCommon,
+ LocationBasedMobileViewModel(commonImpl, statusBarPipelineFlags, debugTint = Color.CYAN)
class QsMobileIconViewModel(
commonImpl: MobileIconViewModelCommon,
- logger: ConnectivityPipelineLogger,
-) : MobileIconViewModelCommon, LocationBasedMobileViewModel(commonImpl, logger) {
- override val tint: Flow<Int> =
- flowOf(Color.GREEN)
- .distinctUntilChanged()
- .logOutputChange(logger, "QS tint(${commonImpl.subscriptionId})")
-}
+ statusBarPipelineFlags: StatusBarPipelineFlags,
+) :
+ MobileIconViewModelCommon,
+ LocationBasedMobileViewModel(commonImpl, statusBarPipelineFlags, debugTint = Color.GREEN)
class KeyguardMobileIconViewModel(
commonImpl: MobileIconViewModelCommon,
- logger: ConnectivityPipelineLogger,
-) : MobileIconViewModelCommon, LocationBasedMobileViewModel(commonImpl, logger) {
- override val tint: Flow<Int> =
- flowOf(Color.MAGENTA)
- .distinctUntilChanged()
- .logOutputChange(logger, "KEYGUARD tint(${commonImpl.subscriptionId})")
-}
+ statusBarPipelineFlags: StatusBarPipelineFlags,
+) :
+ MobileIconViewModelCommon,
+ LocationBasedMobileViewModel(commonImpl, statusBarPipelineFlags, debugTint = Color.MAGENTA)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
index b9318b181aaf..24370d221ade 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
import androidx.annotation.VisibleForTesting
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.phone.StatusBarLocation
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
@@ -41,6 +42,7 @@ constructor(
private val logger: ConnectivityPipelineLogger,
private val constants: ConnectivityConstants,
@Application private val scope: CoroutineScope,
+ private val statusBarPipelineFlags: StatusBarPipelineFlags,
) {
@VisibleForTesting val mobileIconSubIdCache = mutableMapOf<Int, MobileIconViewModel>()
@@ -60,7 +62,11 @@ constructor(
)
.also { mobileIconSubIdCache[subId] = it }
- return LocationBasedMobileViewModel.viewModelForLocation(common, logger, location)
+ return LocationBasedMobileViewModel.viewModelForLocation(
+ common,
+ statusBarPipelineFlags,
+ location,
+ )
}
private fun removeInvalidModelsFromCache(subIds: List<Int>) {
@@ -75,6 +81,7 @@ constructor(
private val logger: ConnectivityPipelineLogger,
private val constants: ConnectivityConstants,
@Application private val scope: CoroutineScope,
+ private val statusBarPipelineFlags: StatusBarPipelineFlags,
) {
fun create(subscriptionIdsFlow: StateFlow<List<Int>>): MobileIconsViewModel {
return MobileIconsViewModel(
@@ -83,6 +90,7 @@ constructor(
logger,
constants,
scope,
+ statusBarPipelineFlags,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt
new file mode 100644
index 000000000000..f67876b50233
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.shared.ui.binder
+
+import com.android.systemui.statusbar.StatusBarIconView
+
+/**
+ * Defines interface for an object that acts as the binding between a modern status bar view and its
+ * view-model.
+ *
+ * Users of the view binder classes in the modern status bar pipeline should use this to control the
+ * binder after it is bound.
+ */
+interface ModernStatusBarViewBinding {
+ /** Returns true if the icon should be visible and false otherwise. */
+ fun getShouldIconBeVisible(): Boolean
+
+ /** Notifies that the visibility state has changed. */
+ fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int)
+
+ /** Notifies that the icon tint has been updated. */
+ fun onIconTintChanged(newTint: Int)
+
+ /** Notifies that the decor tint has been updated (used only for the dot). */
+ fun onDecorTintChanged(newTint: Int)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
new file mode 100644
index 000000000000..cc0ec548716d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.shared.ui.view
+
+import android.content.Context
+import android.graphics.Rect
+import android.util.AttributeSet
+import android.view.Gravity
+import com.android.systemui.R
+import com.android.systemui.plugins.DarkIconDispatcher
+import com.android.systemui.statusbar.BaseStatusBarFrameLayout
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
+import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
+
+/**
+ * A new and more modern implementation of [BaseStatusBarFrameLayout] that gets updated by view
+ * binders communicating via [ModernStatusBarViewBinding].
+ */
+open class ModernStatusBarView(context: Context, attrs: AttributeSet?) :
+ BaseStatusBarFrameLayout(context, attrs) {
+
+ private lateinit var slot: String
+ private lateinit var binding: ModernStatusBarViewBinding
+
+ @StatusBarIconView.VisibleState
+ private var iconVisibleState: Int = STATE_HIDDEN
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ binding.onVisibilityStateChanged(value)
+ }
+
+ override fun getSlot() = slot
+
+ override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) {
+ val newTint = DarkIconDispatcher.getTint(areas, this, tint)
+ binding.onIconTintChanged(newTint)
+ binding.onDecorTintChanged(newTint)
+ }
+
+ override fun setStaticDrawableColor(color: Int) {
+ binding.onIconTintChanged(color)
+ }
+
+ override fun setDecorColor(color: Int) {
+ binding.onDecorTintChanged(color)
+ }
+
+ override fun setVisibleState(@StatusBarIconView.VisibleState state: Int, animate: Boolean) {
+ iconVisibleState = state
+ }
+
+ @StatusBarIconView.VisibleState
+ override fun getVisibleState(): Int {
+ return iconVisibleState
+ }
+
+ override fun isIconVisible(): Boolean {
+ return binding.getShouldIconBeVisible()
+ }
+
+ /**
+ * Initializes this view.
+ *
+ * Creates a dot view, and uses [bindingCreator] to get and set the binding.
+ */
+ fun initView(slot: String, bindingCreator: () -> ModernStatusBarViewBinding) {
+ // The dot view requires [slot] to be set, and the [binding] may require an instantiated dot
+ // view. So, this is the required order.
+ this.slot = slot
+ initDotView()
+ this.binding = bindingCreator.invoke()
+ }
+
+ /**
+ * Creates a [StatusBarIconView] that is always in DOT mode and adds it to this view.
+ *
+ * Mostly duplicated from [com.android.systemui.statusbar.StatusBarWifiView] and
+ * [com.android.systemui.statusbar.StatusBarMobileView].
+ */
+ private fun initDotView() {
+ // TODO(b/238425913): Could we just have this dot view be part of the layout with a dot
+ // drawable so we don't need to inflate it manually? Would that not work with animations?
+ val dotView =
+ StatusBarIconView(mContext, slot, null).also {
+ it.id = R.id.status_bar_dot
+ // Hard-code this view to always be in the DOT state so that whenever it's visible
+ // it will show a dot
+ it.visibleState = STATE_DOT
+ }
+
+ val width = mContext.resources.getDimensionPixelSize(R.dimen.status_bar_icon_size)
+ val lp = LayoutParams(width, width)
+ lp.gravity = Gravity.CENTER_VERTICAL or Gravity.START
+ addView(dotView, lp)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
index a682a5711a6f..4251d18357f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
@@ -23,6 +23,33 @@ import com.android.systemui.log.table.Diffable
/** Provides information about the current wifi network. */
sealed class WifiNetworkModel : Diffable<WifiNetworkModel> {
+ /**
+ * A model representing that we couldn't fetch any wifi information.
+ *
+ * This is only used with [DisabledWifiRepository], where [WifiManager] is null.
+ */
+ object Unavailable : WifiNetworkModel() {
+ override fun toString() = "WifiNetwork.Unavailable"
+ override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) {
+ if (prevVal is Unavailable) {
+ return
+ }
+
+ logFull(row)
+ }
+
+ override fun logFull(row: TableRowLogger) {
+ row.logChange(COL_NETWORK_TYPE, TYPE_UNAVAILABLE)
+ row.logChange(COL_NETWORK_ID, NETWORK_ID_DEFAULT)
+ row.logChange(COL_VALIDATED, false)
+ row.logChange(COL_LEVEL, LEVEL_DEFAULT)
+ row.logChange(COL_SSID, null)
+ row.logChange(COL_PASSPOINT_ACCESS_POINT, false)
+ row.logChange(COL_ONLINE_SIGN_UP, false)
+ row.logChange(COL_PASSPOINT_NAME, null)
+ }
+ }
+
/** A model representing that we have no active wifi network. */
object Inactive : WifiNetworkModel() {
override fun toString() = "WifiNetwork.Inactive"
@@ -87,13 +114,8 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> {
/**
* The wifi signal level, guaranteed to be 0 <= level <= 4.
- *
- * Null if we couldn't fetch the level for some reason.
- *
- * TODO(b/238425913): The level will only be null if we have a null WifiManager. Is there a
- * way we can guarantee a non-null WifiManager?
*/
- val level: Int? = null,
+ val level: Int,
/** See [android.net.wifi.WifiInfo.ssid]. */
val ssid: String? = null,
@@ -108,7 +130,7 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> {
val passpointProviderFriendlyName: String? = null,
) : WifiNetworkModel() {
init {
- require(level == null || level in MIN_VALID_LEVEL..MAX_VALID_LEVEL) {
+ require(level in MIN_VALID_LEVEL..MAX_VALID_LEVEL) {
"0 <= wifi level <= 4 required; level was $level"
}
}
@@ -125,11 +147,7 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> {
row.logChange(COL_VALIDATED, isValidated)
}
if (prevVal !is Active || prevVal.level != level) {
- if (level != null) {
- row.logChange(COL_LEVEL, level)
- } else {
- row.logChange(COL_LEVEL, LEVEL_DEFAULT)
- }
+ row.logChange(COL_LEVEL, level)
}
if (prevVal !is Active || prevVal.ssid != ssid) {
row.logChange(COL_SSID, ssid)
@@ -190,6 +208,7 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> {
}
const val TYPE_CARRIER_MERGED = "CarrierMerged"
+const val TYPE_UNAVAILABLE = "Unavailable"
const val TYPE_INACTIVE = "Inactive"
const val TYPE_ACTIVE = "Active"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
index 53525f254e1d..ac4d55c3a29c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
@@ -34,3 +34,13 @@ interface WifiRepository {
/** Observable for the current wifi network activity. */
val wifiActivity: StateFlow<DataActivityModel>
}
+
+/**
+ * A no-op interface used for Dagger bindings.
+ *
+ * [WifiRepositorySwitcher] needs to inject the "real" wifi repository, which could either be the
+ * full [WifiRepositoryImpl] or just [DisabledWifiRepository]. Having this interface lets us bind
+ * [RealWifiRepository], and then [WifiRepositorySwitcher] will automatically get the correct real
+ * repository.
+ */
+interface RealWifiRepository : WifiRepository
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
index be86620e01f3..2cb81c809716 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
@@ -58,7 +58,7 @@ import kotlinx.coroutines.flow.stateIn
class WifiRepositorySwitcher
@Inject
constructor(
- private val realImpl: WifiRepositoryImpl,
+ private val realImpl: RealWifiRepository,
private val demoImpl: DemoWifiRepository,
private val demoModeController: DemoModeController,
@Application scope: CoroutineScope,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
index 7890074cf8a2..be3d7d4e65c4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
@@ -89,7 +89,7 @@ constructor(
WifiNetworkModel.Active(
networkId = DEMO_NET_ID,
isValidated = validated ?: true,
- level = level,
+ level = level ?: 0,
ssid = ssid,
// These fields below aren't supported in demo mode, since they aren't needed to satisfy
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
new file mode 100644
index 000000000000..5d4a6664a19a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/**
+ * Implementation of wifi repository used when wifi is permanently disabled on the device.
+ *
+ * This repo should only exist when [WifiManager] is null, which means that we can never fetch any
+ * wifi information.
+ */
+@SysUISingleton
+class DisabledWifiRepository @Inject constructor() : RealWifiRepository {
+ override val isWifiEnabled: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow()
+
+ override val isWifiDefault: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow()
+
+ override val wifiNetwork: StateFlow<WifiNetworkModel> = MutableStateFlow(NETWORK).asStateFlow()
+
+ override val wifiActivity: StateFlow<DataActivityModel> =
+ MutableStateFlow(ACTIVITY).asStateFlow()
+
+ companion object {
+ private val NETWORK = WifiNetworkModel.Unavailable
+ private val ACTIVITY = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index c8c94e102999..c47c20d280c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -29,7 +29,6 @@ import android.net.NetworkRequest
import android.net.wifi.WifiInfo
import android.net.wifi.WifiManager
import android.net.wifi.WifiManager.TrafficStateCallback
-import android.util.Log
import com.android.settingslib.Utils
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -40,11 +39,11 @@ import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -53,12 +52,9 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
-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.flowOf
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.stateIn
@@ -68,178 +64,177 @@ import kotlinx.coroutines.flow.stateIn
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
@SuppressLint("MissingPermission")
-class WifiRepositoryImpl @Inject constructor(
+class WifiRepositoryImpl
+@Inject
+constructor(
broadcastDispatcher: BroadcastDispatcher,
connectivityManager: ConnectivityManager,
logger: ConnectivityPipelineLogger,
@WifiTableLog wifiTableLogBuffer: TableLogBuffer,
@Main mainExecutor: Executor,
@Application scope: CoroutineScope,
- wifiManager: WifiManager?,
-) : WifiRepository {
+ wifiManager: WifiManager,
+) : RealWifiRepository {
- private val wifiStateChangeEvents: Flow<Unit> = broadcastDispatcher.broadcastFlow(
- IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION)
- )
- .logInputChange(logger, "WIFI_STATE_CHANGED_ACTION intent")
+ private val wifiStateChangeEvents: Flow<Unit> =
+ broadcastDispatcher
+ .broadcastFlow(IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION))
+ .logInputChange(logger, "WIFI_STATE_CHANGED_ACTION intent")
private val wifiNetworkChangeEvents: MutableSharedFlow<Unit> =
MutableSharedFlow(extraBufferCapacity = 1)
+ // Because [WifiManager] doesn't expose a wifi enabled change listener, we do it
+ // internally by fetching [WifiManager.isWifiEnabled] whenever we think the state may
+ // have changed.
override val isWifiEnabled: StateFlow<Boolean> =
- if (wifiManager == null) {
- MutableStateFlow(false).asStateFlow()
- } else {
- // Because [WifiManager] doesn't expose a wifi enabled change listener, we do it
- // internally by fetching [WifiManager.isWifiEnabled] whenever we think the state may
- // have changed.
- merge(wifiNetworkChangeEvents, wifiStateChangeEvents)
- .mapLatest { wifiManager.isWifiEnabled }
- .distinctUntilChanged()
- .logDiffsForTable(
- wifiTableLogBuffer,
- columnPrefix = "",
- columnName = "isWifiEnabled",
- initialValue = wifiManager.isWifiEnabled,
- )
- .stateIn(
- scope = scope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = wifiManager.isWifiEnabled
- )
- }
+ merge(wifiNetworkChangeEvents, wifiStateChangeEvents)
+ .mapLatest { wifiManager.isWifiEnabled }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ wifiTableLogBuffer,
+ columnPrefix = "",
+ columnName = "isWifiEnabled",
+ initialValue = wifiManager.isWifiEnabled,
+ )
+ .stateIn(
+ scope = scope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = wifiManager.isWifiEnabled,
+ )
- override val isWifiDefault: StateFlow<Boolean> = conflatedCallbackFlow {
- // Note: This callback doesn't do any logging because we already log every network change
- // in the [wifiNetwork] callback.
- val callback = object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
- override fun onCapabilitiesChanged(
- network: Network,
- networkCapabilities: NetworkCapabilities
- ) {
- // This method will always be called immediately after the network becomes the
- // default, in addition to any time the capabilities change while the network is
- // the default.
- // If this network contains valid wifi info, then wifi is the default network.
- val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities)
- trySend(wifiInfo != null)
- }
+ override val isWifiDefault: StateFlow<Boolean> =
+ conflatedCallbackFlow {
+ // Note: This callback doesn't do any logging because we already log every network
+ // change in the [wifiNetwork] callback.
+ val callback =
+ object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
+ override fun onCapabilitiesChanged(
+ network: Network,
+ networkCapabilities: NetworkCapabilities
+ ) {
+ // This method will always be called immediately after the network
+ // becomes the default, in addition to any time the capabilities change
+ // while the network is the default.
+ // If this network contains valid wifi info, then wifi is the default
+ // network.
+ val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities)
+ trySend(wifiInfo != null)
+ }
- override fun onLost(network: Network) {
- // The system no longer has a default network, so wifi is definitely not default.
- trySend(false)
- }
- }
+ override fun onLost(network: Network) {
+ // The system no longer has a default network, so wifi is definitely not
+ // default.
+ trySend(false)
+ }
+ }
- connectivityManager.registerDefaultNetworkCallback(callback)
- awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
- }
- .distinctUntilChanged()
- .logDiffsForTable(
- wifiTableLogBuffer,
- columnPrefix = "",
- columnName = "isWifiDefault",
- initialValue = false,
- )
- .stateIn(
- scope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = false
- )
+ connectivityManager.registerDefaultNetworkCallback(callback)
+ awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
+ }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ wifiTableLogBuffer,
+ columnPrefix = "",
+ columnName = "isWifiDefault",
+ initialValue = false,
+ )
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
- override val wifiNetwork: StateFlow<WifiNetworkModel> = conflatedCallbackFlow {
- var currentWifi: WifiNetworkModel = WIFI_NETWORK_DEFAULT
+ override val wifiNetwork: StateFlow<WifiNetworkModel> =
+ conflatedCallbackFlow {
+ var currentWifi: WifiNetworkModel = WIFI_NETWORK_DEFAULT
- val callback = object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
- override fun onCapabilitiesChanged(
- network: Network,
- networkCapabilities: NetworkCapabilities
- ) {
- logger.logOnCapabilitiesChanged(network, networkCapabilities)
+ val callback =
+ object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
+ override fun onCapabilitiesChanged(
+ network: Network,
+ networkCapabilities: NetworkCapabilities
+ ) {
+ logger.logOnCapabilitiesChanged(network, networkCapabilities)
- wifiNetworkChangeEvents.tryEmit(Unit)
+ wifiNetworkChangeEvents.tryEmit(Unit)
- val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities)
- if (wifiInfo?.isPrimary == true) {
- val wifiNetworkModel = createWifiNetworkModel(
- wifiInfo,
- network,
- networkCapabilities,
- wifiManager,
- )
- logger.logTransformation(
- WIFI_NETWORK_CALLBACK_NAME,
- oldValue = currentWifi,
- newValue = wifiNetworkModel
- )
- currentWifi = wifiNetworkModel
- trySend(wifiNetworkModel)
- }
- }
+ val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities)
+ if (wifiInfo?.isPrimary == true) {
+ val wifiNetworkModel =
+ createWifiNetworkModel(
+ wifiInfo,
+ network,
+ networkCapabilities,
+ wifiManager,
+ )
+ logger.logTransformation(
+ WIFI_NETWORK_CALLBACK_NAME,
+ oldValue = currentWifi,
+ newValue = wifiNetworkModel,
+ )
+ currentWifi = wifiNetworkModel
+ trySend(wifiNetworkModel)
+ }
+ }
- override fun onLost(network: Network) {
- logger.logOnLost(network)
+ override fun onLost(network: Network) {
+ logger.logOnLost(network)
- wifiNetworkChangeEvents.tryEmit(Unit)
+ wifiNetworkChangeEvents.tryEmit(Unit)
- val wifi = currentWifi
- if (wifi is WifiNetworkModel.Active && wifi.networkId == network.getNetId()) {
- val newNetworkModel = WifiNetworkModel.Inactive
- logger.logTransformation(
- WIFI_NETWORK_CALLBACK_NAME,
- oldValue = wifi,
- newValue = newNetworkModel
- )
- currentWifi = newNetworkModel
- trySend(newNetworkModel)
- }
- }
- }
+ val wifi = currentWifi
+ if (
+ wifi is WifiNetworkModel.Active &&
+ wifi.networkId == network.getNetId()
+ ) {
+ val newNetworkModel = WifiNetworkModel.Inactive
+ logger.logTransformation(
+ WIFI_NETWORK_CALLBACK_NAME,
+ oldValue = wifi,
+ newValue = newNetworkModel,
+ )
+ currentWifi = newNetworkModel
+ trySend(newNetworkModel)
+ }
+ }
+ }
- connectivityManager.registerNetworkCallback(WIFI_NETWORK_CALLBACK_REQUEST, callback)
+ connectivityManager.registerNetworkCallback(WIFI_NETWORK_CALLBACK_REQUEST, callback)
- awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
- }
- .distinctUntilChanged()
- .logDiffsForTable(
- wifiTableLogBuffer,
- columnPrefix = "wifiNetwork",
- initialValue = WIFI_NETWORK_DEFAULT,
- )
- // There will be multiple wifi icons in different places that will frequently
- // subscribe/unsubscribe to flows as the views attach/detach. Using [stateIn] ensures that
- // new subscribes will get the latest value immediately upon subscription. Otherwise, the
- // views could show stale data. See b/244173280.
- .stateIn(
- scope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = WIFI_NETWORK_DEFAULT
- )
+ awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
+ }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ wifiTableLogBuffer,
+ columnPrefix = "wifiNetwork",
+ initialValue = WIFI_NETWORK_DEFAULT,
+ )
+ // There will be multiple wifi icons in different places that will frequently
+ // subscribe/unsubscribe to flows as the views attach/detach. Using [stateIn] ensures
+ // that new subscribes will get the latest value immediately upon subscription.
+ // Otherwise, the views could show stale data. See b/244173280.
+ .stateIn(
+ scope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = WIFI_NETWORK_DEFAULT,
+ )
override val wifiActivity: StateFlow<DataActivityModel> =
- if (wifiManager == null) {
- Log.w(SB_LOGGING_TAG, "Null WifiManager; skipping activity callback")
- flowOf(ACTIVITY_DEFAULT)
- } else {
- conflatedCallbackFlow {
- val callback = TrafficStateCallback { state ->
- logger.logInputChange("onTrafficStateChange", prettyPrintActivity(state))
- trySend(state.toWifiDataActivityModel())
- }
- wifiManager.registerTrafficStateCallback(mainExecutor, callback)
- awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
+ conflatedCallbackFlow {
+ val callback = TrafficStateCallback { state ->
+ logger.logInputChange("onTrafficStateChange", prettyPrintActivity(state))
+ trySend(state.toWifiDataActivityModel())
}
+ wifiManager.registerTrafficStateCallback(mainExecutor, callback)
+ awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
}
- .logDiffsForTable(
- wifiTableLogBuffer,
- columnPrefix = ACTIVITY_PREFIX,
- initialValue = ACTIVITY_DEFAULT,
- )
- .stateIn(
- scope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = ACTIVITY_DEFAULT
- )
+ .logDiffsForTable(
+ wifiTableLogBuffer,
+ columnPrefix = ACTIVITY_PREFIX,
+ initialValue = ACTIVITY_DEFAULT,
+ )
+ .stateIn(
+ scope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = ACTIVITY_DEFAULT,
+ )
companion object {
private const val ACTIVITY_PREFIX = "wifiActivity"
@@ -271,19 +266,19 @@ class WifiRepositoryImpl @Inject constructor(
wifiInfo: WifiInfo,
network: Network,
networkCapabilities: NetworkCapabilities,
- wifiManager: WifiManager?,
+ wifiManager: WifiManager,
): WifiNetworkModel {
return if (wifiInfo.isCarrierMerged) {
WifiNetworkModel.CarrierMerged
} else {
WifiNetworkModel.Active(
- network.getNetId(),
- isValidated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED),
- level = wifiManager?.calculateSignalLevel(wifiInfo.rssi),
- wifiInfo.ssid,
- wifiInfo.isPasspointAp,
- wifiInfo.isOsuAp,
- wifiInfo.passpointProviderFriendlyName
+ network.getNetId(),
+ isValidated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED),
+ level = wifiManager.calculateSignalLevel(wifiInfo.rssi),
+ wifiInfo.ssid,
+ wifiInfo.isPasspointAp,
+ wifiInfo.isOsuAp,
+ wifiInfo.passpointProviderFriendlyName
)
}
}
@@ -308,4 +303,28 @@ class WifiRepositoryImpl @Inject constructor(
private const val WIFI_NETWORK_CALLBACK_NAME = "wifiNetworkModel"
}
+
+ @SysUISingleton
+ class Factory
+ @Inject
+ constructor(
+ private val broadcastDispatcher: BroadcastDispatcher,
+ private val connectivityManager: ConnectivityManager,
+ private val logger: ConnectivityPipelineLogger,
+ @WifiTableLog private val wifiTableLogBuffer: TableLogBuffer,
+ @Main private val mainExecutor: Executor,
+ @Application private val scope: CoroutineScope,
+ ) {
+ fun create(wifiManager: WifiManager): WifiRepositoryImpl {
+ return WifiRepositoryImpl(
+ broadcastDispatcher,
+ connectivityManager,
+ logger,
+ wifiTableLogBuffer,
+ mainExecutor,
+ scope,
+ wifiManager,
+ )
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
index 93041ceb4200..980560ab5d58 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
@@ -65,6 +65,7 @@ class WifiInteractorImpl @Inject constructor(
override val ssid: Flow<String?> = wifiRepository.wifiNetwork.map { info ->
when (info) {
+ is WifiNetworkModel.Unavailable -> null
is WifiNetworkModel.Inactive -> null
is WifiNetworkModel.CarrierMerged -> null
is WifiNetworkModel.Active -> when {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
index cc67c84772a5..2aff12c8721d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -30,6 +30,7 @@ import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
import kotlinx.coroutines.InternalCoroutinesApi
@@ -49,31 +50,12 @@ import kotlinx.coroutines.launch
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
object WifiViewBinder {
- /**
- * Defines interface for an object that acts as the binding between the view and its view-model.
- *
- * Users of the [WifiViewBinder] class should use this to control the binder after it is bound.
- */
- interface Binding {
- /** Returns true if the wifi icon should be visible and false otherwise. */
- fun getShouldIconBeVisible(): Boolean
-
- /** Notifies that the visibility state has changed. */
- fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int)
-
- /** Notifies that the icon tint has been updated. */
- fun onIconTintChanged(newTint: Int)
-
- /** Notifies that the decor tint has been updated (used only for the dot). */
- fun onDecorTintChanged(newTint: Int)
- }
-
/** Binds the view to the view-model, continuing to update the former based on the latter. */
@JvmStatic
fun bind(
view: ViewGroup,
viewModel: LocationBasedWifiViewModel,
- ): Binding {
+ ): ModernStatusBarViewBinding {
val groupView = view.requireViewById<ViewGroup>(R.id.wifi_group)
val iconView = view.requireViewById<ImageView>(R.id.wifi_signal)
val dotView = view.requireViewById<StatusBarIconView>(R.id.status_bar_dot)
@@ -148,7 +130,7 @@ object WifiViewBinder {
}
}
- return object : Binding {
+ return object : ModernStatusBarViewBinding {
override fun getShouldIconBeVisible(): Boolean {
return viewModel.wifiIcon.value is WifiIcon.Visible
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
index be7782c37cfd..7a734862fe1b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
@@ -16,17 +16,12 @@
package com.android.systemui.statusbar.pipeline.wifi.ui.view
+import android.annotation.SuppressLint
import android.content.Context
-import android.graphics.Rect
import android.util.AttributeSet
-import android.view.Gravity
import android.view.LayoutInflater
import com.android.systemui.R
-import com.android.systemui.plugins.DarkIconDispatcher
-import com.android.systemui.statusbar.BaseStatusBarFrameLayout
-import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
-import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
+import com.android.systemui.statusbar.pipeline.shared.ui.view.ModernStatusBarView
import com.android.systemui.statusbar.pipeline.wifi.ui.binder.WifiViewBinder
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
@@ -36,83 +31,14 @@ import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWi
*/
class ModernStatusBarWifiView(
context: Context,
- attrs: AttributeSet?
-) : BaseStatusBarFrameLayout(context, attrs) {
-
- private lateinit var slot: String
- private lateinit var binding: WifiViewBinder.Binding
-
- @StatusBarIconView.VisibleState
- private var iconVisibleState: Int = STATE_HIDDEN
- set(value) {
- if (field == value) {
- return
- }
- field = value
- binding.onVisibilityStateChanged(value)
- }
-
- override fun getSlot() = slot
-
- override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) {
- val newTint = DarkIconDispatcher.getTint(areas, this, tint)
- binding.onIconTintChanged(newTint)
- binding.onDecorTintChanged(newTint)
- }
-
- override fun setStaticDrawableColor(color: Int) {
- binding.onIconTintChanged(color)
- }
-
- override fun setDecorColor(color: Int) {
- binding.onDecorTintChanged(color)
- }
-
- override fun setVisibleState(@StatusBarIconView.VisibleState state: Int, animate: Boolean) {
- iconVisibleState = state
- }
-
- @StatusBarIconView.VisibleState
- override fun getVisibleState(): Int {
- return iconVisibleState
- }
-
- override fun isIconVisible(): Boolean {
- return binding.getShouldIconBeVisible()
- }
-
- private fun initView(
- slotName: String,
- wifiViewModel: LocationBasedWifiViewModel,
- ) {
- slot = slotName
- initDotView()
- binding = WifiViewBinder.bind(this, wifiViewModel)
- }
-
- // Mostly duplicated from [com.android.systemui.statusbar.StatusBarWifiView].
- private fun initDotView() {
- // TODO(b/238425913): Could we just have this dot view be part of
- // R.layout.new_status_bar_wifi_group with a dot drawable so we don't need to inflate it
- // manually? Would that not work with animations?
- val dotView = StatusBarIconView(mContext, slot, null).also {
- it.id = R.id.status_bar_dot
- // Hard-code this view to always be in the DOT state so that whenever it's visible it
- // will show a dot
- it.visibleState = STATE_DOT
- }
-
- val width = mContext.resources.getDimensionPixelSize(R.dimen.status_bar_icon_size)
- val lp = LayoutParams(width, width)
- lp.gravity = Gravity.CENTER_VERTICAL or Gravity.START
- addView(dotView, lp)
- }
-
+ attrs: AttributeSet?,
+) : ModernStatusBarView(context, attrs) {
companion object {
/**
* Inflates a new instance of [ModernStatusBarWifiView], binds it to a view model, and
* returns it.
*/
+ @SuppressLint("InflateParams")
@JvmStatic
fun constructAndBind(
context: Context,
@@ -123,7 +49,7 @@ class ModernStatusBarWifiView(
LayoutInflater.from(context).inflate(R.layout.new_status_bar_wifi_group, null)
as ModernStatusBarWifiView
).also {
- it.initView(slot, wifiViewModel)
+ it.initView(slot) { WifiViewBinder.bind(it, wifiViewModel) }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
index a4615cc897cf..02c3a652cc8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
@@ -47,7 +47,7 @@ abstract class LocationBasedWifiViewModel(
/** True if the airplane spacer view should be visible. */
val isAirplaneSpacerVisible: Flow<Boolean>,
) {
- val useDebugColoring: Boolean = statusBarPipelineFlags.useWifiDebugColoring()
+ val useDebugColoring: Boolean = statusBarPipelineFlags.useDebugColoring()
val defaultColor: Int =
if (useDebugColoring) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
index ab464cc78905..824b5972ba4b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
@@ -82,6 +82,7 @@ constructor(
/** Returns the icon to use based on the given network. */
private fun WifiNetworkModel.icon(): WifiIcon {
return when (this) {
+ is WifiNetworkModel.Unavailable -> WifiIcon.Hidden
is WifiNetworkModel.CarrierMerged -> WifiIcon.Hidden
is WifiNetworkModel.Inactive -> WifiIcon.Visible(
res = WIFI_NO_NETWORK,
@@ -89,27 +90,23 @@ constructor(
"${context.getString(WIFI_NO_CONNECTION)},${context.getString(NO_INTERNET)}"
)
)
- is WifiNetworkModel.Active ->
- when (this.level) {
- null -> WifiIcon.Hidden
- else -> {
- val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[this.level])
- when {
- this.isValidated ->
- WifiIcon.Visible(
- WIFI_FULL_ICONS[this.level],
- ContentDescription.Loaded(levelDesc)
- )
- else ->
- WifiIcon.Visible(
- WIFI_NO_INTERNET_ICONS[this.level],
- ContentDescription.Loaded(
- "$levelDesc,${context.getString(NO_INTERNET)}"
- )
- )
- }
- }
+ is WifiNetworkModel.Active -> {
+ val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[this.level])
+ when {
+ this.isValidated ->
+ WifiIcon.Visible(
+ WIFI_FULL_ICONS[this.level],
+ ContentDescription.Loaded(levelDesc),
+ )
+ else ->
+ WifiIcon.Visible(
+ WIFI_NO_INTERNET_ICONS[this.level],
+ ContentDescription.Loaded(
+ "$levelDesc,${context.getString(NO_INTERNET)}"
+ ),
+ )
}
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
index 3e111e6de785..302d6a9ca1b7 100644
--- a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
@@ -38,7 +38,7 @@ class StylusManager
@Inject
constructor(
private val inputManager: InputManager,
- private val bluetoothAdapter: BluetoothAdapter,
+ private val bluetoothAdapter: BluetoothAdapter?,
@Background private val handler: Handler,
@Background private val executor: Executor,
) : InputManager.InputDeviceListener, BluetoothAdapter.OnMetadataChangedListener {
@@ -141,7 +141,7 @@ constructor(
}
private fun onStylusBluetoothConnected(btAddress: String) {
- val device: BluetoothDevice = bluetoothAdapter.getRemoteDevice(btAddress) ?: return
+ val device: BluetoothDevice = bluetoothAdapter?.getRemoteDevice(btAddress) ?: return
try {
bluetoothAdapter.addOnMetadataChangedListener(device, executor, this)
} catch (e: IllegalArgumentException) {
@@ -150,7 +150,7 @@ constructor(
}
private fun onStylusBluetoothDisconnected(btAddress: String) {
- val device: BluetoothDevice = bluetoothAdapter.getRemoteDevice(btAddress) ?: return
+ val device: BluetoothDevice = bluetoothAdapter?.getRemoteDevice(btAddress) ?: return
try {
bluetoothAdapter.removeOnMetadataChangedListener(device, this)
} catch (e: IllegalArgumentException) {
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index 52980c3c1f9b..04b1a5016989 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -65,26 +65,27 @@ import javax.inject.Inject
* in the list of notifications until the user dismisses them.
*
* Only one chipbar may be shown at a time.
- * TODO(b/245610654): Should we just display whichever chipbar was most recently requested, or do we
- * need to maintain a priority ordering?
*/
@SysUISingleton
-open class ChipbarCoordinator @Inject constructor(
- context: Context,
- logger: ChipbarLogger,
- windowManager: WindowManager,
- @Main mainExecutor: DelayableExecutor,
- accessibilityManager: AccessibilityManager,
- configurationController: ConfigurationController,
- dumpManager: DumpManager,
- powerManager: PowerManager,
- private val falsingManager: FalsingManager,
- private val falsingCollector: FalsingCollector,
- private val viewUtil: ViewUtil,
- private val vibratorHelper: VibratorHelper,
- wakeLockBuilder: WakeLock.Builder,
- systemClock: SystemClock,
-) : TemporaryViewDisplayController<ChipbarInfo, ChipbarLogger>(
+open class ChipbarCoordinator
+@Inject
+constructor(
+ context: Context,
+ logger: ChipbarLogger,
+ windowManager: WindowManager,
+ @Main mainExecutor: DelayableExecutor,
+ accessibilityManager: AccessibilityManager,
+ configurationController: ConfigurationController,
+ dumpManager: DumpManager,
+ powerManager: PowerManager,
+ private val falsingManager: FalsingManager,
+ private val falsingCollector: FalsingCollector,
+ private val viewUtil: ViewUtil,
+ private val vibratorHelper: VibratorHelper,
+ wakeLockBuilder: WakeLock.Builder,
+ systemClock: SystemClock,
+) :
+ TemporaryViewDisplayController<ChipbarInfo, ChipbarLogger>(
context,
logger,
windowManager,
@@ -96,18 +97,14 @@ open class ChipbarCoordinator @Inject constructor(
R.layout.chipbar,
wakeLockBuilder,
systemClock,
-) {
+ ) {
private lateinit var parent: ChipbarRootView
- override val windowLayoutParams = commonWindowLayoutParams.apply {
- gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
- }
+ override val windowLayoutParams =
+ commonWindowLayoutParams.apply { gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL) }
- override fun updateView(
- newInfo: ChipbarInfo,
- currentView: ViewGroup
- ) {
+ override fun updateView(newInfo: ChipbarInfo, currentView: ViewGroup) {
logger.logViewUpdate(
newInfo.windowTitle,
newInfo.text.loadText(context),
@@ -123,12 +120,13 @@ open class ChipbarCoordinator @Inject constructor(
// Detect falsing touches on the chip.
parent = currentView.requireViewById(R.id.chipbar_root_view)
- parent.touchHandler = object : Gefingerpoken {
- override fun onTouchEvent(ev: MotionEvent?): Boolean {
- falsingCollector.onTouchEvent(ev)
- return false
+ parent.touchHandler =
+ object : Gefingerpoken {
+ override fun onTouchEvent(ev: MotionEvent?): Boolean {
+ falsingCollector.onTouchEvent(ev)
+ return false
+ }
}
- }
// ---- Start icon ----
val iconView = currentView.requireViewById<CachingIconView>(R.id.start_icon)
@@ -155,10 +153,12 @@ open class ChipbarCoordinator @Inject constructor(
if (newInfo.endItem is ChipbarEndItem.Button) {
TextViewBinder.bind(buttonView, newInfo.endItem.text)
- val onClickListener = View.OnClickListener { clickedView ->
- if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@OnClickListener
- newInfo.endItem.onClickListener.onClick(clickedView)
- }
+ val onClickListener =
+ View.OnClickListener { clickedView ->
+ if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY))
+ return@OnClickListener
+ newInfo.endItem.onClickListener.onClick(clickedView)
+ }
buttonView.setOnClickListener(onClickListener)
buttonView.visibility = View.VISIBLE
@@ -168,21 +168,27 @@ open class ChipbarCoordinator @Inject constructor(
// ---- Overall accessibility ----
val iconDesc = newInfo.startIcon.icon.contentDescription
- val loadedIconDesc = if (iconDesc != null) {
- "${iconDesc.loadContentDescription(context)} "
- } else {
- ""
- }
+ val loadedIconDesc =
+ if (iconDesc != null) {
+ "${iconDesc.loadContentDescription(context)} "
+ } else {
+ ""
+ }
+ val endItemDesc =
+ if (newInfo.endItem is ChipbarEndItem.Loading) {
+ ". ${context.resources.getString(R.string.media_transfer_loading)}."
+ } else {
+ ""
+ }
val chipInnerView = currentView.getInnerView()
- chipInnerView.contentDescription = "$loadedIconDesc${newInfo.text.loadText(context)}"
+ chipInnerView.contentDescription =
+ "$loadedIconDesc${newInfo.text.loadText(context)}$endItemDesc"
chipInnerView.accessibilityLiveRegion = ACCESSIBILITY_LIVE_REGION_ASSERTIVE
maybeGetAccessibilityFocus(newInfo, currentView)
// ---- Haptics ----
- newInfo.vibrationEffect?.let {
- vibratorHelper.vibrate(it)
- }
+ newInfo.vibrationEffect?.let { vibratorHelper.vibrate(it) }
}
private fun maybeGetAccessibilityFocus(info: ChipbarInfo?, view: ViewGroup) {
diff --git a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
index cd21a45be0ce..c570ec8d2cd7 100644
--- a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
@@ -64,7 +64,7 @@ public class CreateUserActivity extends Activity {
private Dialog mGrantAdminDialog;
private Dialog mSetupUserDialog;
private final OnBackInvokedCallback mBackCallback = this::onBackInvoked;
- private Boolean mGrantAdminRights;
+ private boolean mGrantAdminRights;
@Inject
public CreateUserActivity(UserCreator userCreator,
EditUserInfoController editUserInfoController, IActivityManager activityManager,
@@ -83,8 +83,7 @@ public class CreateUserActivity extends Activity {
if (savedInstanceState != null) {
mEditUserInfoController.onRestoreInstanceState(savedInstanceState);
}
-
- if (mUserCreator.isHeadlessSystemUserMode()) {
+ if (mUserCreator.isMultipleAdminEnabled()) {
mGrantAdminDialog = buildGrantAdminDialog();
mGrantAdminDialog.show();
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt b/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt
index 277f670597d3..1811c4d9f930 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt
@@ -87,7 +87,7 @@ constructor(
userManager.setUserAdmin(userId)
}
- fun isHeadlessSystemUserMode(): Boolean {
- return UserManager.isHeadlessSystemUserMode()
+ fun isMultipleAdminEnabled(): Boolean {
+ return UserManager.isMultipleAdminEnabled()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt
index 34e78eb8c2eb..e9a2789bb5c8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt
@@ -16,29 +16,25 @@
package com.android.keyguard.mediator
+import android.os.Handler
+import android.os.Looper
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
-
import com.android.systemui.SysuiTestCase
-import com.android.systemui.keyguard.ScreenLifecycle
import com.android.systemui.unfold.FoldAodAnimationController
import com.android.systemui.unfold.SysUIUnfoldComponent
import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation
-import com.android.systemui.util.concurrency.FakeExecution
import com.android.systemui.util.mockito.capture
-
-import java.util.Optional
-
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import java.util.Optional
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -55,6 +51,8 @@ class ScreenOnCoordinatorTest : SysuiTestCase() {
@Captor
private lateinit var readyCaptor: ArgumentCaptor<Runnable>
+ private val testHandler = Handler(Looper.getMainLooper())
+
private lateinit var screenOnCoordinator: ScreenOnCoordinator
@Before
@@ -68,6 +66,7 @@ class ScreenOnCoordinatorTest : SysuiTestCase() {
screenOnCoordinator = ScreenOnCoordinator(
Optional.of(unfoldComponent),
+ testHandler
)
}
@@ -77,6 +76,7 @@ class ScreenOnCoordinatorTest : SysuiTestCase() {
onUnfoldOverlayReady()
onFoldAodReady()
+ waitHandlerIdle(testHandler)
// Should be called when both unfold overlay and keyguard drawn ready
verify(runnable).run()
@@ -87,8 +87,10 @@ class ScreenOnCoordinatorTest : SysuiTestCase() {
// Recreate with empty unfoldComponent
screenOnCoordinator = ScreenOnCoordinator(
Optional.empty(),
+ testHandler
)
screenOnCoordinator.onScreenTurningOn(runnable)
+ waitHandlerIdle(testHandler)
// Should be called when only keyguard drawn
verify(runnable).run()
@@ -103,4 +105,8 @@ class ScreenOnCoordinatorTest : SysuiTestCase() {
verify(foldAodAnimationController).onScreenTurningOn(capture(readyCaptor))
readyCaptor.value.run()
}
+
+ private fun waitHandlerIdle(handler: Handler) {
+ handler.runWithScissors({}, /* timeout= */ 0)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
index a36105e11514..a4b9b0849457 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
@@ -22,6 +22,7 @@ import static org.mockito.Mockito.verify;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
import androidx.test.filters.SmallTest;
@@ -29,8 +30,12 @@ import com.android.systemui.SysuiTestCase;
import com.android.wm.shell.bubbles.DismissView;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
/** Tests for {@link DismissAnimationController}. */
@SmallTest
@@ -40,10 +45,16 @@ public class DismissAnimationControllerTest extends SysuiTestCase {
private DismissAnimationController mDismissAnimationController;
private DismissView mDismissView;
+ @Rule
+ public MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private AccessibilityManager mAccessibilityManager;
+
@Before
public void setUp() throws Exception {
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
- final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext);
+ final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager);
final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
stubWindowManager);
final MenuView stubMenuView = new MenuView(mContext, stubMenuViewModel,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
index 0cdd6e2ce85e..7356184d4879 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
@@ -31,6 +31,7 @@ import android.testing.TestableLooper;
import android.view.View;
import android.view.ViewPropertyAnimator;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.FlingAnimation;
@@ -43,9 +44,13 @@ import com.android.systemui.SysuiTestCase;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import java.util.Optional;
@@ -61,12 +66,18 @@ public class MenuAnimationControllerTest extends SysuiTestCase {
private MenuView mMenuView;
private TestMenuAnimationController mMenuAnimationController;
+ @Rule
+ public MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private AccessibilityManager mAccessibilityManager;
+
@Before
public void setUp() throws Exception {
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
stubWindowManager);
- final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext);
+ final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager);
mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance));
mViewPropertyAnimator = spy(mMenuView.animate());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
index e62a3295a7e2..06340afb4892 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
@@ -16,16 +16,23 @@
package com.android.systemui.accessibility.floatingmenu;
+import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
+
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
+import android.content.Context;
+import android.content.res.Configuration;
import android.testing.AndroidTestingRunner;
+import android.view.accessibility.AccessibilityManager;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -34,6 +41,10 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
/** Tests for {@link MenuInfoRepository}. */
@RunWith(AndroidTestingRunner.class)
@SmallTest
@@ -42,13 +53,28 @@ public class MenuInfoRepositoryTest extends SysuiTestCase {
public MockitoRule mockito = MockitoJUnit.rule();
@Mock
+ private AccessibilityManager mAccessibilityManager;
+
+ @Mock
private MenuInfoRepository.OnSettingsContentsChanged mMockSettingsContentsChanged;
private MenuInfoRepository mMenuInfoRepository;
+ private final List<String> mShortcutTargets = new ArrayList<>();
@Before
public void setUp() {
- mMenuInfoRepository = new MenuInfoRepository(mContext, mMockSettingsContentsChanged);
+ mContext.addMockSystemService(Context.ACCESSIBILITY_SERVICE, mAccessibilityManager);
+ mShortcutTargets.add(MAGNIFICATION_CONTROLLER_NAME);
+ doReturn(mShortcutTargets).when(mAccessibilityManager).getAccessibilityShortcutTargets(
+ anyInt());
+
+ mMenuInfoRepository = new MenuInfoRepository(mContext, mAccessibilityManager,
+ mMockSettingsContentsChanged);
+ }
+
+ @After
+ public void tearDown() {
+ mShortcutTargets.clear();
}
@Test
@@ -64,4 +90,14 @@ public class MenuInfoRepositoryTest extends SysuiTestCase {
verify(mMockSettingsContentsChanged).onFadeEffectInfoChanged(any(MenuFadeEffectInfo.class));
}
+
+ @Test
+ public void localeChange_verifyTargetFeaturesChanged() {
+ final Configuration configuration = new Configuration();
+ configuration.setLocale(Locale.TAIWAN);
+
+ mMenuInfoRepository.mComponentCallbacks.onConfigurationChanged(configuration);
+
+ verify(mMockSettingsContentsChanged).onTargetFeaturesChanged(any());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
index 78ee627a9a2f..f17b1cfe3c88 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
@@ -29,6 +29,7 @@ import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
@@ -56,6 +57,9 @@ public class MenuItemAccessibilityDelegateTest extends SysuiTestCase {
public MockitoRule mockito = MockitoJUnit.rule();
@Mock
+ private AccessibilityManager mAccessibilityManager;
+
+ @Mock
private DismissAnimationController.DismissCallback mStubDismissCallback;
private RecyclerView mStubListView;
@@ -69,7 +73,7 @@ public class MenuItemAccessibilityDelegateTest extends SysuiTestCase {
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
stubWindowManager);
- final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext);
+ final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager);
final int halfScreenHeight =
stubWindowManager.getCurrentWindowMetrics().getBounds().height() / 2;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
index d29ebb86686f..ed9562d83872 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
@@ -31,6 +31,7 @@ import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.MotionEvent;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.test.filters.SmallTest;
@@ -42,8 +43,12 @@ import com.android.wm.shell.bubbles.DismissView;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import java.util.ArrayList;
import java.util.Collections;
@@ -64,10 +69,16 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
private RecyclerView mStubListView;
private DismissView mDismissView;
+ @Rule
+ public MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private AccessibilityManager mAccessibilityManager;
+
@Before
public void setUp() throws Exception {
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
- final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext);
+ final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager);
final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
windowManager);
mStubMenuView = new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
index 742ee53e99b6..5a1a6db92742 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
@@ -29,6 +29,7 @@ import android.graphics.drawable.GradientDrawable;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
import androidx.test.filters.SmallTest;
@@ -37,8 +38,12 @@ import com.android.systemui.SysuiTestCase;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
/** Tests for {@link MenuView}. */
@RunWith(AndroidTestingRunner.class)
@@ -52,12 +57,18 @@ public class MenuViewTest extends SysuiTestCase {
private String mLastPosition;
private MenuViewAppearance mStubMenuViewAppearance;
+ @Rule
+ public MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private AccessibilityManager mAccessibilityManager;
+
@Before
public void setUp() throws Exception {
mUiModeManager = mContext.getSystemService(UiModeManager.class);
mNightMode = mUiModeManager.getNightMode();
mUiModeManager.setNightMode(MODE_NIGHT_YES);
- final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext);
+ final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager);
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
mStubMenuViewAppearance = new MenuViewAppearance(mContext, stubWindowManager);
mMenuView = spy(new MenuView(mContext, stubMenuViewModel, mStubMenuViewAppearance));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
index a56990f40b90..3528e14dbd80 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
@@ -41,6 +41,7 @@ import android.os.BatteryManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.UserHandle;
+import android.provider.Settings;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -55,6 +56,8 @@ import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.NotificationChannels;
+import com.android.systemui.util.settings.FakeSettings;
+import com.android.systemui.util.settings.GlobalSettings;
import org.junit.Before;
import org.junit.Test;
@@ -73,6 +76,7 @@ public class PowerNotificationWarningsTest extends SysuiTestCase {
public static final String FORMATTED_45M = "0h 45m";
public static final String FORMATTED_HOUR = "1h 0m";
private final NotificationManager mMockNotificationManager = mock(NotificationManager.class);
+ private final GlobalSettings mGlobalSettings = new FakeSettings();
private PowerNotificationWarnings mPowerNotificationWarnings;
@Mock
@@ -104,7 +108,8 @@ public class PowerNotificationWarningsTest extends SysuiTestCase {
ActivityStarter starter = mDependency.injectMockDependency(ActivityStarter.class);
BroadcastSender broadcastSender = mDependency.injectMockDependency(BroadcastSender.class);
mPowerNotificationWarnings = new PowerNotificationWarnings(wrapper, starter,
- broadcastSender, () -> mBatteryController, mDialogLaunchAnimator, mUiEventLogger);
+ broadcastSender, () -> mBatteryController, mDialogLaunchAnimator, mUiEventLogger,
+ mGlobalSettings);
BatteryStateSnapshot snapshot = new BatteryStateSnapshot(100, false, false, 1,
BatteryManager.BATTERY_HEALTH_GOOD, 5, 15);
mPowerNotificationWarnings.updateSnapshot(snapshot);
@@ -146,6 +151,16 @@ public class PowerNotificationWarningsTest extends SysuiTestCase {
}
@Test
+ public void testDisableLowBatteryReminder_noNotification() {
+ mGlobalSettings.putInt(Settings.Global.LOW_POWER_MODE_REMINDER_ENABLED, 0);
+
+ mPowerNotificationWarnings.showLowBatteryWarning(false);
+
+ verify(mMockNotificationManager, times(0))
+ .notifyAsUser(anyString(), eq(SystemMessage.NOTE_POWER_LOW), any(), any());
+ }
+
+ @Test
public void testShowLowBatteryNotification_NotifyAsUser() {
mPowerNotificationWarnings.showLowBatteryWarning(false);
verify(mMockNotificationManager, times(1))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index ffe918d36d6f..42ef9c2914ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -151,7 +151,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
@Test
public void transitionToFullShade_setsAlphaUsingShadeInterpolator() {
QSFragment fragment = resumeAndGetFragment();
- setStatusBarState(StatusBarState.SHADE);
+ setStatusBarCurrentAndUpcomingState(StatusBarState.SHADE);
boolean isTransitioningToFullShade = true;
float transitionProgress = 0.5f;
float squishinessFraction = 0.5f;
@@ -167,7 +167,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
public void
transitionToFullShade_onKeyguard_noBouncer_setsAlphaUsingLinearInterpolator() {
QSFragment fragment = resumeAndGetFragment();
- setStatusBarState(KEYGUARD);
+ setStatusBarCurrentAndUpcomingState(KEYGUARD);
when(mQSPanelController.isBouncerInTransit()).thenReturn(false);
boolean isTransitioningToFullShade = true;
float transitionProgress = 0.5f;
@@ -183,7 +183,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
public void
transitionToFullShade_onKeyguard_bouncerActive_setsAlphaUsingBouncerInterpolator() {
QSFragment fragment = resumeAndGetFragment();
- setStatusBarState(KEYGUARD);
+ setStatusBarCurrentAndUpcomingState(KEYGUARD);
when(mQSPanelController.isBouncerInTransit()).thenReturn(true);
boolean isTransitioningToFullShade = true;
float transitionProgress = 0.5f;
@@ -482,6 +482,15 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
assertEquals(175, mediaHostClip.bottom);
}
+ @Test
+ public void testQsUpdatesQsAnimatorWithUpcomingState() {
+ QSFragment fragment = resumeAndGetFragment();
+ setStatusBarCurrentAndUpcomingState(SHADE);
+ fragment.onUpcomingStateChanged(KEYGUARD);
+
+ verify(mQSAnimator).setOnKeyguard(true);
+ }
+
@Override
protected Fragment instantiate(Context context, String className, Bundle arguments) {
MockitoAnnotations.initMocks(this);
@@ -591,8 +600,9 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
return getFragment();
}
- private void setStatusBarState(int statusBarState) {
+ private void setStatusBarCurrentAndUpcomingState(int statusBarState) {
when(mStatusBarStateController.getState()).thenReturn(statusBarState);
+ when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(statusBarState);
getFragment().onStateChanged(statusBarState);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index d6a9ee325b2e..53cd71f1bdf9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
import kotlinx.coroutines.flow.MutableStateFlow
// TODO(b/261632894): remove this in favor of the real impl or DemoMobileConnectionRepository
@@ -29,12 +30,11 @@ class FakeMobileConnectionRepository(
private val _connectionInfo = MutableStateFlow(MobileConnectionModel())
override val connectionInfo = _connectionInfo
+ override val numberOfLevels = MutableStateFlow(DEFAULT_NUM_LEVELS)
+
private val _dataEnabled = MutableStateFlow(true)
override val dataEnabled = _dataEnabled
- private val _isDefaultDataSubscription = MutableStateFlow(true)
- override val isDefaultDataSubscription = _isDefaultDataSubscription
-
override val cdmaRoaming = MutableStateFlow(false)
override val networkName =
@@ -47,8 +47,4 @@ class FakeMobileConnectionRepository(
fun setDataEnabled(enabled: Boolean) {
_dataEnabled.value = enabled
}
-
- fun setIsDefaultDataSubscription(isDefault: Boolean) {
- _isDefaultDataSubscription.value = isDefault
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
index 7f93328ee95e..49d4bdc88c82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
@@ -57,9 +57,6 @@ class FakeMobileConnectionsRepository(
private val _activeMobileDataSubscriptionId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
override val activeMobileDataSubscriptionId = _activeMobileDataSubscriptionId
- private val _defaultDataSubId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
- override val defaultDataSubId = _defaultDataSubId
-
private val _mobileConnectivity = MutableStateFlow(MobileConnectivityModel())
override val defaultMobileNetworkConnectivity = _mobileConnectivity
@@ -84,10 +81,6 @@ class FakeMobileConnectionsRepository(
_subscriptions.value = subs
}
- fun setDefaultDataSubId(id: Int) {
- _defaultDataSubId.value = id
- }
-
fun setMobileConnectivity(model: MobileConnectivityModel) {
_mobileConnectivity.value = model
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index c63dd2a2318c..d6b8c0dbc59d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -61,6 +61,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetwork
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.UnknownNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.toNetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
@@ -117,7 +118,6 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
telephonyManager,
globalSettings,
fakeBroadcastDispatcher,
- connectionsRepo.defaultDataSubId,
connectionsRepo.globalMobileDataSettingChangedEvent,
mobileMappings,
IMMEDIATE,
@@ -319,7 +319,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
val type = NETWORK_TYPE_LTE
- val expected = DefaultNetworkType(type, mobileMappings.toIconKey(type))
+ val expected = DefaultNetworkType(mobileMappings.toIconKey(type))
val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) }
callback.onDisplayInfoChanged(ti)
@@ -336,7 +336,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
val type = OVERRIDE_NETWORK_TYPE_LTE_CA
- val expected = OverrideNetworkType(type, mobileMappings.toIconKeyOverride(type))
+ val expected = OverrideNetworkType(mobileMappings.toIconKeyOverride(type))
val ti =
mock<TelephonyDisplayInfo>().also {
whenever(it.networkType).thenReturn(type)
@@ -380,33 +380,6 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
}
@Test
- fun isDefaultDataSubscription_isDefault() =
- runBlocking(IMMEDIATE) {
- connectionsRepo.setDefaultDataSubId(SUB_1_ID)
-
- var latest: Boolean? = null
- val job = underTest.isDefaultDataSubscription.onEach { latest = it }.launchIn(this)
-
- assertThat(latest).isTrue()
-
- job.cancel()
- }
-
- @Test
- fun isDefaultDataSubscription_isNotDefault() =
- runBlocking(IMMEDIATE) {
- // Our subId is SUB_1_ID
- connectionsRepo.setDefaultDataSubId(123)
-
- var latest: Boolean? = null
- val job = underTest.isDefaultDataSubscription.onEach { latest = it }.launchIn(this)
-
- assertThat(latest).isFalse()
-
- job.cancel()
- }
-
- @Test
fun isDataConnectionAllowed_subIdSettingUpdate_valueUpdated() =
runBlocking(IMMEDIATE) {
val subIdSettingName = "${Settings.Global.MOBILE_DATA}$SUB_1_ID"
@@ -431,6 +404,17 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
}
@Test
+ fun numberOfLevels_isDefault() =
+ runBlocking(IMMEDIATE) {
+ var latest: Int? = null
+ val job = underTest.numberOfLevels.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(DEFAULT_NUM_LEVELS)
+
+ job.cancel()
+ }
+
+ @Test
fun `roaming - cdma - queries telephony manager`() =
runBlocking(IMMEDIATE) {
var latest: Boolean? = null
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index b8cd7a4f6e0a..0da15e239932 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -307,35 +307,6 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
}
@Test
- fun testDefaultDataSubId_updatesOnBroadcast() =
- runBlocking(IMMEDIATE) {
- var latest: Int? = null
- val job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this)
-
- fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
- receiver.onReceive(
- context,
- Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
- .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_2_ID)
- )
- }
-
- assertThat(latest).isEqualTo(SUB_2_ID)
-
- fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
- receiver.onReceive(
- context,
- Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
- .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_1_ID)
- )
- }
-
- assertThat(latest).isEqualTo(SUB_1_ID)
-
- job.cancel()
- }
-
- @Test
fun mobileConnectivity_default() {
assertThat(underTest.defaultMobileNetworkConnectivity.value)
.isEqualTo(MobileConnectivityModel(isConnected = false, isValidated = false))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
index ff72715b281f..a29146b01668 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -21,6 +21,7 @@ import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import kotlinx.coroutines.flow.MutableStateFlow
@@ -65,7 +66,7 @@ class FakeMobileIconInteractor(
private val _level = MutableStateFlow(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
override val level = _level
- private val _numberOfLevels = MutableStateFlow(4)
+ private val _numberOfLevels = MutableStateFlow(DEFAULT_NUM_LEVELS)
override val numberOfLevels = _numberOfLevels
fun setIconGroup(group: SignalIcon.MobileIconGroup) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index 5abe33523cc6..61e13b85db6c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
import android.telephony.CellSignalStrength
-import android.telephony.SubscriptionInfo
import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import androidx.test.filters.SmallTest
import com.android.settingslib.SignalIcon.MobileIconGroup
@@ -34,7 +33,6 @@ import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobi
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.THREE_G
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -178,12 +176,26 @@ class MobileIconInteractorTest : SysuiTestCase() {
}
@Test
+ fun numberOfLevels_comesFromRepo() =
+ runBlocking(IMMEDIATE) {
+ var latest: Int? = null
+ val job = underTest.numberOfLevels.onEach { latest = it }.launchIn(this)
+
+ connectionRepository.numberOfLevels.value = 5
+ assertThat(latest).isEqualTo(5)
+
+ connectionRepository.numberOfLevels.value = 4
+ assertThat(latest).isEqualTo(4)
+
+ job.cancel()
+ }
+
+ @Test
fun iconGroup_three_g() =
runBlocking(IMMEDIATE) {
connectionRepository.setConnectionInfo(
MobileConnectionModel(
- resolvedNetworkType =
- DefaultNetworkType(THREE_G, mobileMappingsProxy.toIconKey(THREE_G))
+ resolvedNetworkType = DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
),
)
@@ -200,8 +212,7 @@ class MobileIconInteractorTest : SysuiTestCase() {
runBlocking(IMMEDIATE) {
connectionRepository.setConnectionInfo(
MobileConnectionModel(
- resolvedNetworkType =
- DefaultNetworkType(THREE_G, mobileMappingsProxy.toIconKey(THREE_G))
+ resolvedNetworkType = DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
),
)
@@ -212,7 +223,6 @@ class MobileIconInteractorTest : SysuiTestCase() {
MobileConnectionModel(
resolvedNetworkType =
DefaultNetworkType(
- FOUR_G,
mobileMappingsProxy.toIconKey(FOUR_G),
),
),
@@ -230,10 +240,7 @@ class MobileIconInteractorTest : SysuiTestCase() {
connectionRepository.setConnectionInfo(
MobileConnectionModel(
resolvedNetworkType =
- OverrideNetworkType(
- FIVE_G_OVERRIDE,
- mobileMappingsProxy.toIconKeyOverride(FIVE_G_OVERRIDE)
- )
+ OverrideNetworkType(mobileMappingsProxy.toIconKeyOverride(FIVE_G_OVERRIDE))
),
)
@@ -251,10 +258,7 @@ class MobileIconInteractorTest : SysuiTestCase() {
connectionRepository.setConnectionInfo(
MobileConnectionModel(
resolvedNetworkType =
- DefaultNetworkType(
- NETWORK_TYPE_UNKNOWN,
- mobileMappingsProxy.toIconKey(NETWORK_TYPE_UNKNOWN)
- ),
+ DefaultNetworkType(mobileMappingsProxy.toIconKey(NETWORK_TYPE_UNKNOWN)),
),
)
@@ -509,8 +513,6 @@ class MobileIconInteractorTest : SysuiTestCase() {
private const val CDMA_LEVEL = 2
private const val SUB_1_ID = 1
- private val SUB_1 =
- mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
private val DEFAULT_NAME = NetworkNameModel.Default("test default name")
private val DERIVED_NAME = NetworkNameModel.Derived("test derived name")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
new file mode 100644
index 000000000000..a2c1209f5a40
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.view
+
+import android.content.res.ColorStateList
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import android.testing.ViewUtils
+import android.view.View
+import android.widget.ImageView
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.QsMobileIconViewModel
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+@OptIn(ExperimentalCoroutinesApi::class)
+class ModernStatusBarMobileViewTest : SysuiTestCase() {
+
+ private lateinit var testableLooper: TestableLooper
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
+ @Mock private lateinit var tableLogBuffer: TableLogBuffer
+ @Mock private lateinit var logger: ConnectivityPipelineLogger
+ @Mock private lateinit var constants: ConnectivityConstants
+
+ private lateinit var viewModel: LocationBasedMobileViewModel
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ testableLooper = TestableLooper.get(this)
+
+ val interactor = FakeMobileIconInteractor(tableLogBuffer)
+
+ val viewModelCommon =
+ MobileIconViewModel(
+ subscriptionId = 1,
+ interactor,
+ logger,
+ constants,
+ testScope.backgroundScope,
+ )
+ viewModel = QsMobileIconViewModel(viewModelCommon, statusBarPipelineFlags)
+ }
+
+ // Note: The following tests are more like integration tests, since they stand up a full
+ // [WifiViewModel] and test the interactions between the view, view-binder, and view-model.
+
+ @Test
+ fun setVisibleState_icon_iconShownDotHidden() {
+ val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
+
+ view.setVisibleState(StatusBarIconView.STATE_ICON, /* animate= */ false)
+
+ ViewUtils.attachView(view)
+ testableLooper.processAllMessages()
+
+ assertThat(view.getGroupView().visibility).isEqualTo(View.VISIBLE)
+ assertThat(view.getDotView().visibility).isEqualTo(View.GONE)
+
+ ViewUtils.detachView(view)
+ }
+
+ @Test
+ fun setVisibleState_dot_iconHiddenDotShown() {
+ val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
+
+ view.setVisibleState(StatusBarIconView.STATE_DOT, /* animate= */ false)
+
+ ViewUtils.attachView(view)
+ testableLooper.processAllMessages()
+
+ assertThat(view.getGroupView().visibility).isEqualTo(View.INVISIBLE)
+ assertThat(view.getDotView().visibility).isEqualTo(View.VISIBLE)
+
+ ViewUtils.detachView(view)
+ }
+
+ @Test
+ fun setVisibleState_hidden_iconAndDotHidden() {
+ val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
+
+ view.setVisibleState(StatusBarIconView.STATE_HIDDEN, /* animate= */ false)
+
+ ViewUtils.attachView(view)
+ testableLooper.processAllMessages()
+
+ assertThat(view.getGroupView().visibility).isEqualTo(View.INVISIBLE)
+ assertThat(view.getDotView().visibility).isEqualTo(View.INVISIBLE)
+
+ ViewUtils.detachView(view)
+ }
+
+ @Test
+ fun isIconVisible_alwaysTrue() {
+ val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
+
+ ViewUtils.attachView(view)
+ testableLooper.processAllMessages()
+
+ assertThat(view.isIconVisible).isTrue()
+
+ ViewUtils.detachView(view)
+ }
+
+ @Test
+ fun onDarkChanged_iconHasNewColor() {
+ whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false)
+ val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
+ ViewUtils.attachView(view)
+ testableLooper.processAllMessages()
+
+ val color = 0x12345678
+ view.onDarkChanged(arrayListOf(), 1.0f, color)
+ testableLooper.processAllMessages()
+
+ assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color))
+
+ ViewUtils.detachView(view)
+ }
+
+ @Test
+ fun setStaticDrawableColor_iconHasNewColor() {
+ whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false)
+ val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
+ ViewUtils.attachView(view)
+ testableLooper.processAllMessages()
+
+ val color = 0x23456789
+ view.setStaticDrawableColor(color)
+ testableLooper.processAllMessages()
+
+ assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color))
+
+ ViewUtils.detachView(view)
+ }
+
+ private fun View.getGroupView(): View {
+ return this.requireViewById(R.id.mobile_group)
+ }
+
+ private fun View.getIconView(): ImageView {
+ return this.requireViewById(R.id.mobile_signal)
+ }
+
+ private fun View.getDotView(): View {
+ return this.requireViewById(R.id.status_bar_dot)
+ }
+}
+
+private const val SLOT_NAME = "TestSlotName"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
index 043d55a73076..c960a06e6bb2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
@@ -20,6 +20,7 @@ import androidx.test.filters.SmallTest
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModelTest.Companion.defaultSignal
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
@@ -45,6 +46,7 @@ class LocationBasedMobileIconViewModelTest : SysuiTestCase() {
private lateinit var qsIcon: QsMobileIconViewModel
private lateinit var keyguardIcon: KeyguardMobileIconViewModel
private lateinit var interactor: FakeMobileIconInteractor
+ @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var constants: ConnectivityConstants
@Mock private lateinit var tableLogBuffer: TableLogBuffer
@@ -68,9 +70,9 @@ class LocationBasedMobileIconViewModelTest : SysuiTestCase() {
commonImpl =
MobileIconViewModel(SUB_1_ID, interactor, logger, constants, testScope.backgroundScope)
- homeIcon = HomeMobileIconViewModel(commonImpl, logger)
- qsIcon = QsMobileIconViewModel(commonImpl, logger)
- keyguardIcon = KeyguardMobileIconViewModel(commonImpl, logger)
+ homeIcon = HomeMobileIconViewModel(commonImpl, statusBarPipelineFlags)
+ qsIcon = QsMobileIconViewModel(commonImpl, statusBarPipelineFlags)
+ keyguardIcon = KeyguardMobileIconViewModel(commonImpl, statusBarPipelineFlags)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
index d6cb76260f0b..58b50c7e7e6d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.phone.StatusBarLocation
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
@@ -45,6 +46,7 @@ class MobileIconsViewModelTest : SysuiTestCase() {
private lateinit var underTest: MobileIconsViewModel
private val interactor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+ @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var constants: ConnectivityConstants
@@ -67,6 +69,7 @@ class MobileIconsViewModelTest : SysuiTestCase() {
logger,
constants,
testScope.backgroundScope,
+ statusBarPipelineFlags,
)
interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
new file mode 100644
index 000000000000..3fe69837a761
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.shared.ui.view
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
+import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
+import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+class ModernStatusBarViewTest : SysuiTestCase() {
+
+ private lateinit var binding: TestBinding
+
+ @Test
+ fun initView_hasCorrectSlot() {
+ val view = ModernStatusBarView(context, null)
+ val binding = TestBinding()
+
+ view.initView("slotName") { binding }
+
+ assertThat(view.slot).isEqualTo("slotName")
+ }
+
+ @Test
+ fun getVisibleState_icon_returnsIcon() {
+ val view = createAndInitView()
+
+ view.setVisibleState(STATE_ICON, /* animate= */ false)
+
+ assertThat(view.visibleState).isEqualTo(STATE_ICON)
+ }
+
+ @Test
+ fun getVisibleState_dot_returnsDot() {
+ val view = createAndInitView()
+
+ view.setVisibleState(STATE_DOT, /* animate= */ false)
+
+ assertThat(view.visibleState).isEqualTo(STATE_DOT)
+ }
+
+ @Test
+ fun getVisibleState_hidden_returnsHidden() {
+ val view = createAndInitView()
+
+ view.setVisibleState(STATE_HIDDEN, /* animate= */ false)
+
+ assertThat(view.visibleState).isEqualTo(STATE_HIDDEN)
+ }
+
+ @Test
+ fun onDarkChanged_bindingReceivesIconAndDecorTint() {
+ val view = createAndInitView()
+
+ view.onDarkChanged(arrayListOf(), 1.0f, 0x12345678)
+
+ assertThat(binding.iconTint).isEqualTo(0x12345678)
+ assertThat(binding.decorTint).isEqualTo(0x12345678)
+ }
+
+ @Test
+ fun setStaticDrawableColor_bindingReceivesIconTint() {
+ val view = createAndInitView()
+
+ view.setStaticDrawableColor(0x12345678)
+
+ assertThat(binding.iconTint).isEqualTo(0x12345678)
+ }
+
+ @Test
+ fun setDecorColor_bindingReceivesDecorColor() {
+ val view = createAndInitView()
+
+ view.setDecorColor(0x23456789)
+
+ assertThat(binding.decorTint).isEqualTo(0x23456789)
+ }
+
+ @Test
+ fun isIconVisible_usesBinding_true() {
+ val view = createAndInitView()
+
+ binding.shouldIconBeVisibleInternal = true
+
+ assertThat(view.isIconVisible).isEqualTo(true)
+ }
+
+ @Test
+ fun isIconVisible_usesBinding_false() {
+ val view = createAndInitView()
+
+ binding.shouldIconBeVisibleInternal = false
+
+ assertThat(view.isIconVisible).isEqualTo(false)
+ }
+
+ private fun createAndInitView(): ModernStatusBarView {
+ val view = ModernStatusBarView(context, null)
+ binding = TestBinding()
+ view.initView(SLOT_NAME) { binding }
+ return view
+ }
+
+ inner class TestBinding : ModernStatusBarViewBinding {
+ var iconTint: Int? = null
+ var decorTint: Int? = null
+ var onVisibilityStateChangedCalled: Boolean = false
+
+ var shouldIconBeVisibleInternal: Boolean = true
+
+ override fun onIconTintChanged(newTint: Int) {
+ iconTint = newTint
+ }
+
+ override fun onDecorTintChanged(newTint: Int) {
+ decorTint = newTint
+ }
+
+ override fun onVisibilityStateChanged(state: Int) {
+ onVisibilityStateChangedCalled = true
+ }
+
+ override fun getShouldIconBeVisible(): Boolean {
+ return shouldIconBeVisibleInternal
+ }
+ }
+}
+
+private const val SLOT_NAME = "TestSlotName"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt
index 30fd308433e4..30ac8d432e8a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt
@@ -34,12 +34,6 @@ class WifiNetworkModelTest : SysuiTestCase() {
}
}
- @Test
- fun active_levelNull_noException() {
- WifiNetworkModel.Active(NETWORK_ID, level = null)
- // No assert, just need no crash
- }
-
@Test(expected = IllegalArgumentException::class)
fun active_levelNegative_exceptionThrown() {
WifiNetworkModel.Active(NETWORK_ID, level = MIN_VALID_LEVEL - 1)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt
new file mode 100644
index 000000000000..3c4e85bd231e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+class DisabledWifiRepositoryTest : SysuiTestCase() {
+
+ private lateinit var underTest: DisabledWifiRepository
+
+ @Before
+ fun setUp() {
+ underTest = DisabledWifiRepository()
+ }
+
+ @Test
+ fun enabled_alwaysFalse() {
+ assertThat(underTest.isWifiEnabled.value).isEqualTo(false)
+ }
+
+ @Test
+ fun default_alwaysFalse() {
+ assertThat(underTest.isWifiDefault.value).isEqualTo(false)
+ }
+
+ @Test
+ fun network_alwaysUnavailable() {
+ assertThat(underTest.wifiNetwork.value).isEqualTo(WifiNetworkModel.Unavailable)
+ }
+
+ @Test
+ fun activity_alwaysFalse() {
+ assertThat(underTest.wifiActivity.value)
+ .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
index befb2901d4d5..8f07615b19b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
@@ -33,7 +33,6 @@ import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
@@ -98,13 +97,6 @@ class WifiRepositoryImplTest : SysuiTestCase() {
}
@Test
- fun isWifiEnabled_nullWifiManager_getsFalse() = runBlocking(IMMEDIATE) {
- underTest = createRepo(wifiManagerToUse = null)
-
- assertThat(underTest.isWifiEnabled.value).isFalse()
- }
-
- @Test
fun isWifiEnabled_initiallyGetsWifiManagerValue() = runBlocking(IMMEDIATE) {
whenever(wifiManager.isWifiEnabled).thenReturn(true)
@@ -721,21 +713,6 @@ class WifiRepositoryImplTest : SysuiTestCase() {
}
@Test
- fun wifiActivity_nullWifiManager_receivesDefault() = runBlocking(IMMEDIATE) {
- underTest = createRepo(wifiManagerToUse = null)
-
- var latest: DataActivityModel? = null
- val job = underTest
- .wifiActivity
- .onEach { latest = it }
- .launchIn(this)
-
- assertThat(latest).isEqualTo(ACTIVITY_DEFAULT)
-
- job.cancel()
- }
-
- @Test
fun wifiActivity_callbackGivesNone_activityFlowHasNone() = runBlocking(IMMEDIATE) {
var latest: DataActivityModel? = null
val job = underTest
@@ -801,7 +778,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
job.cancel()
}
- private fun createRepo(wifiManagerToUse: WifiManager? = wifiManager): WifiRepositoryImpl {
+ private fun createRepo(): WifiRepositoryImpl {
return WifiRepositoryImpl(
broadcastDispatcher,
connectivityManager,
@@ -809,7 +786,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
tableLogger,
executor,
scope,
- wifiManagerToUse,
+ wifiManager,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
index 2ecb17b7fae0..01d59f96c221 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
@@ -52,6 +52,22 @@ class WifiInteractorImplTest : SysuiTestCase() {
}
@Test
+ fun ssid_unavailableNetwork_outputsNull() =
+ runBlocking(IMMEDIATE) {
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Unavailable)
+
+ var latest: String? = "default"
+ val job = underTest
+ .ssid
+ .onEach { latest = it }
+ .launchIn(this)
+
+ assertThat(latest).isNull()
+
+ job.cancel()
+ }
+
+ @Test
fun ssid_inactiveNetwork_outputsNull() = runBlocking(IMMEDIATE) {
wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
@@ -85,6 +101,7 @@ class WifiInteractorImplTest : SysuiTestCase() {
fun ssid_isPasspointAccessPoint_outputsPasspointName() = runBlocking(IMMEDIATE) {
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
networkId = 1,
+ level = 1,
isPasspointAccessPoint = true,
passpointProviderFriendlyName = "friendly",
))
@@ -104,6 +121,7 @@ class WifiInteractorImplTest : SysuiTestCase() {
fun ssid_isOnlineSignUpForPasspoint_outputsPasspointName() = runBlocking(IMMEDIATE) {
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
networkId = 1,
+ level = 1,
isOnlineSignUpForPasspointAccessPoint = true,
passpointProviderFriendlyName = "friendly",
))
@@ -123,6 +141,7 @@ class WifiInteractorImplTest : SysuiTestCase() {
fun ssid_unknownSsid_outputsNull() = runBlocking(IMMEDIATE) {
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
networkId = 1,
+ level = 1,
ssid = WifiManager.UNKNOWN_SSID,
))
@@ -141,6 +160,7 @@ class WifiInteractorImplTest : SysuiTestCase() {
fun ssid_validSsid_outputsSsid() = runBlocking(IMMEDIATE) {
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
networkId = 1,
+ level = 1,
ssid = "MyAwesomeWifiNetwork",
))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index 59c10cd6df7c..b8ace2f04a61 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.pipeline.wifi.ui.view
import android.content.res.ColorStateList
-import android.graphics.Rect
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
@@ -27,7 +26,6 @@ import android.widget.ImageView
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
-import com.android.systemui.lifecycle.InstantTaskExecutorRule
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
@@ -52,8 +50,6 @@ import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import org.junit.Before
-import org.junit.Ignore
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
@@ -70,7 +66,8 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() {
private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@Mock
private lateinit var logger: ConnectivityPipelineLogger
- @Mock private lateinit var tableLogBuffer: TableLogBuffer
+ @Mock
+ private lateinit var tableLogBuffer: TableLogBuffer
@Mock
private lateinit var connectivityConstants: ConnectivityConstants
@Mock
@@ -83,9 +80,6 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() {
private lateinit var scope: CoroutineScope
private lateinit var airplaneModeViewModel: AirplaneModeViewModel
- @JvmField @Rule
- val instantTaskExecutor = InstantTaskExecutorRule()
-
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -118,40 +112,6 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() {
).home
}
- @Test
- fun constructAndBind_hasCorrectSlot() {
- val view = ModernStatusBarWifiView.constructAndBind(context, "slotName", viewModel)
-
- assertThat(view.slot).isEqualTo("slotName")
- }
-
- @Test
- fun getVisibleState_icon_returnsIcon() {
- val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
-
- view.setVisibleState(STATE_ICON, /* animate= */ false)
-
- assertThat(view.visibleState).isEqualTo(STATE_ICON)
- }
-
- @Test
- fun getVisibleState_dot_returnsDot() {
- val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
-
- view.setVisibleState(STATE_DOT, /* animate= */ false)
-
- assertThat(view.visibleState).isEqualTo(STATE_DOT)
- }
-
- @Test
- fun getVisibleState_hidden_returnsHidden() {
- val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
-
- view.setVisibleState(STATE_HIDDEN, /* animate= */ false)
-
- assertThat(view.visibleState).isEqualTo(STATE_HIDDEN)
- }
-
// Note: The following tests are more like integration tests, since they stand up a full
// [WifiViewModel] and test the interactions between the view, view-binder, and view-model.
@@ -235,24 +195,24 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() {
}
@Test
- @Ignore("b/262660044")
fun onDarkChanged_iconHasNewColor() {
- whenever(statusBarPipelineFlags.useWifiDebugColoring()).thenReturn(false)
+ whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false)
val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
ViewUtils.attachView(view)
testableLooper.processAllMessages()
- val areas = ArrayList(listOf(Rect(0, 0, 1000, 1000)))
val color = 0x12345678
- view.onDarkChanged(areas, 1.0f, color)
+ view.onDarkChanged(arrayListOf(), 1.0f, color)
testableLooper.processAllMessages()
assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color))
+
+ ViewUtils.detachView(view)
}
@Test
fun setStaticDrawableColor_iconHasNewColor() {
- whenever(statusBarPipelineFlags.useWifiDebugColoring()).thenReturn(false)
+ whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false)
val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
ViewUtils.attachView(view)
testableLooper.processAllMessages()
@@ -262,6 +222,8 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() {
testableLooper.processAllMessages()
assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color))
+
+ ViewUtils.detachView(view)
}
private fun View.getIconGroupView(): View {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
index 12b93819fc5e..726e813ec414 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
@@ -379,6 +379,12 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase
expected = null,
),
+ // network = Unavailable => not shown
+ TestCase(
+ network = WifiNetworkModel.Unavailable,
+ expected = null,
+ ),
+
// network = Active & validated = false => not shown
TestCase(
network = WifiNetworkModel.Active(NETWORK_ID, isValidated = false, level = 3),
@@ -397,12 +403,6 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase
description = "Full internet level 4 icon",
),
),
-
- // network has null level => not shown
- TestCase(
- network = WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = null),
- expected = null,
- ),
)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index 41584347c0f2..e5cfec9c08c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -228,7 +228,7 @@ class WifiViewModelTest : SysuiTestCase() {
whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
- wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, ssid = null))
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, ssid = null, level = 1))
var activityIn: Boolean? = null
val activityInJob = underTest
@@ -553,7 +553,8 @@ class WifiViewModelTest : SysuiTestCase() {
companion object {
private const val NETWORK_ID = 2
- private val ACTIVE_VALID_WIFI_NETWORK = WifiNetworkModel.Active(NETWORK_ID, ssid = "AB")
+ private val ACTIVE_VALID_WIFI_NETWORK =
+ WifiNetworkModel.Active(NETWORK_ID, ssid = "AB", level = 1)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt
index 58b55602a39c..984de5b67bf5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt
@@ -202,7 +202,7 @@ class StylusManagerTest : SysuiTestCase() {
stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
stylusManager.registerCallback(otherStylusCallback)
- stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
+ stylusManager.onInputDeviceRemoved(STYLUS_DEVICE_ID)
verify(stylusCallback, times(1)).onStylusRemoved(STYLUS_DEVICE_ID)
verify(otherStylusCallback, times(1)).onStylusRemoved(STYLUS_DEVICE_ID)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
index d3411c2b4416..90178c6a0096 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
@@ -124,7 +124,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
)
)
- val contentDescView = getChipbarView().requireViewById<ViewGroup>(R.id.chipbar_inner)
+ val contentDescView = getChipbarView().getInnerView()
assertThat(contentDescView.contentDescription.toString()).contains("loadedCD")
assertThat(contentDescView.contentDescription.toString()).contains("text")
}
@@ -139,11 +139,43 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
)
)
- val contentDescView = getChipbarView().requireViewById<ViewGroup>(R.id.chipbar_inner)
+ val contentDescView = getChipbarView().getInnerView()
assertThat(contentDescView.contentDescription.toString()).isEqualTo("text")
}
@Test
+ fun displayView_contentDescription_endIsLoading() {
+ underTest.displayView(
+ createChipbarInfo(
+ Icon.Resource(R.drawable.ic_cake, ContentDescription.Loaded("loadedCD")),
+ Text.Loaded("text"),
+ endItem = ChipbarEndItem.Loading,
+ )
+ )
+
+ val contentDescView = getChipbarView().getInnerView()
+ val loadingDesc = context.resources.getString(R.string.media_transfer_loading)
+ assertThat(contentDescView.contentDescription.toString()).contains("text")
+ assertThat(contentDescView.contentDescription.toString()).contains(loadingDesc)
+ }
+
+ @Test
+ fun displayView_contentDescription_endNotLoading() {
+ underTest.displayView(
+ createChipbarInfo(
+ Icon.Resource(R.drawable.ic_cake, ContentDescription.Loaded("loadedCD")),
+ Text.Loaded("text"),
+ endItem = ChipbarEndItem.Error,
+ )
+ )
+
+ val contentDescView = getChipbarView().getInnerView()
+ val loadingDesc = context.resources.getString(R.string.media_transfer_loading)
+ assertThat(contentDescView.contentDescription.toString()).contains("text")
+ assertThat(contentDescView.contentDescription.toString()).doesNotContain(loadingDesc)
+ }
+
+ @Test
fun displayView_loadedIcon_correctlyRendered() {
val drawable = context.getDrawable(R.drawable.ic_celebration)!!
@@ -417,6 +449,8 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
)
}
+ private fun ViewGroup.getInnerView() = this.requireViewById<ViewGroup>(R.id.chipbar_inner)
+
private fun ViewGroup.getStartIconView() = this.requireViewById<ImageView>(R.id.start_icon)
private fun ViewGroup.getChipText(): String =
diff --git a/services/api/current.txt b/services/api/current.txt
index 090a4499a591..b173726411f6 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -40,6 +40,7 @@ package com.android.server.am {
public interface ActivityManagerLocal {
method public boolean bindSdkSandboxService(@NonNull android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull String, @NonNull String, int) throws android.os.RemoteException;
method public boolean canStartForegroundService(int, int, @NonNull String);
+ method public void killSdkSandboxClientAppProcess(@NonNull android.os.IBinder);
}
}
@@ -212,6 +213,19 @@ package com.android.server.role {
}
+package com.android.server.security {
+
+ public class FileIntegrityService extends com.android.server.SystemService {
+ method public void onStart();
+ method public static void setUpFsVerity(@NonNull String) throws java.io.IOException;
+ }
+
+ public class KeyChainSystemService extends com.android.server.SystemService {
+ method public void onStart();
+ }
+
+}
+
package com.android.server.stats {
public final class StatsHelper {
diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
index 3ff6ba7e59c0..38c7dd1e230b 100644
--- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java
+++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
@@ -375,7 +375,7 @@ public class BackupHandler extends Handler {
case MSG_RUN_GET_RESTORE_SETS: {
// Like other async operations, this is entered with the wakelock held
- RestoreSet[] sets = null;
+ List<RestoreSet> sets = null;
RestoreGetSetsParams params = (RestoreGetSetsParams) msg.obj;
String callerLogString = "BH/MSG_RUN_GET_RESTORE_SETS";
try {
@@ -394,7 +394,12 @@ public class BackupHandler extends Handler {
} finally {
if (params.observer != null) {
try {
- params.observer.restoreSetsAvailable(sets);
+ if (sets == null) {
+ params.observer.restoreSetsAvailable(null);
+ } else {
+ params.observer.restoreSetsAvailable(
+ sets.toArray(new RestoreSet[0]));
+ }
} catch (RemoteException re) {
Slog.e(TAG, "Unable to report listing to observer");
} catch (Exception e) {
diff --git a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
index d3e4f138f5da..70d7fac09a4f 100644
--- a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
+++ b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
@@ -45,6 +45,7 @@ import com.android.server.backup.params.RestoreParams;
import com.android.server.backup.transport.TransportConnection;
import com.android.server.backup.utils.BackupEligibilityRules;
+import java.util.List;
import java.util.function.BiFunction;
/**
@@ -60,7 +61,7 @@ public class ActiveRestoreSession extends IRestoreSession.Stub {
private final int mUserId;
private final BackupEligibilityRules mBackupEligibilityRules;
@Nullable private final String mPackageName;
- public RestoreSet[] mRestoreSets = null;
+ public List<RestoreSet> mRestoreSets = null;
boolean mEnded = false;
boolean mTimedOut = false;
@@ -174,10 +175,10 @@ public class ActiveRestoreSession extends IRestoreSession.Stub {
}
synchronized (mBackupManagerService.getQueueLock()) {
- for (int i = 0; i < mRestoreSets.length; i++) {
- if (token == mRestoreSets[i].token) {
+ for (int i = 0; i < mRestoreSets.size(); i++) {
+ if (token == mRestoreSets.get(i).token) {
final long oldId = Binder.clearCallingIdentity();
- RestoreSet restoreSet = mRestoreSets[i];
+ RestoreSet restoreSet = mRestoreSets.get(i);
try {
return sendRestoreToHandlerLocked(
(transportClient, listener) ->
@@ -267,10 +268,10 @@ public class ActiveRestoreSession extends IRestoreSession.Stub {
}
synchronized (mBackupManagerService.getQueueLock()) {
- for (int i = 0; i < mRestoreSets.length; i++) {
- if (token == mRestoreSets[i].token) {
+ for (int i = 0; i < mRestoreSets.size(); i++) {
+ if (token == mRestoreSets.get(i).token) {
final long oldId = Binder.clearCallingIdentity();
- RestoreSet restoreSet = mRestoreSets[i];
+ RestoreSet restoreSet = mRestoreSets.get(i);
try {
return sendRestoreToHandlerLocked(
(transportClient, listener) ->
@@ -390,7 +391,7 @@ public class ActiveRestoreSession extends IRestoreSession.Stub {
}
}
- public void setRestoreSets(RestoreSet[] restoreSets) {
+ public void setRestoreSets(List<RestoreSet> restoreSets) {
mRestoreSets = restoreSets;
}
diff --git a/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java b/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java
index 21005bbf8af9..daf3415229ea 100644
--- a/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java
+++ b/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java
@@ -180,11 +180,11 @@ public class BackupTransportClient {
/**
* See {@link IBackupTransport#getAvailableRestoreSets()}
*/
- public RestoreSet[] getAvailableRestoreSets() throws RemoteException {
+ public List<RestoreSet> getAvailableRestoreSets() throws RemoteException {
AndroidFuture<List<RestoreSet>> resultFuture = mTransportFutures.newFuture();
mTransportBinder.getAvailableRestoreSets(resultFuture);
List<RestoreSet> result = getFutureResult(resultFuture);
- return result == null ? null : result.toArray(new RestoreSet[] {});
+ return result;
}
/**
diff --git a/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
index 2904f28fca01..312ab548c746 100644
--- a/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
+++ b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
@@ -19,6 +19,7 @@ package com.android.server.companion.virtual;
import static android.hardware.camera2.CameraInjectionSession.InjectionStatusCallback.ERROR_INJECTION_UNSUPPORTED;
import android.annotation.NonNull;
+import android.annotation.UserIdInt;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -95,6 +96,23 @@ class CameraAccessController extends CameraManager.AvailabilityCallback implemen
}
/**
+ * Returns the userId for which the camera access should be blocked.
+ */
+ @UserIdInt
+ public int getUserId() {
+ return mContext.getUserId();
+ }
+
+ /**
+ * Returns the number of observers currently relying on this controller.
+ */
+ public int getObserverCount() {
+ synchronized (mLock) {
+ return mObserverCount;
+ }
+ }
+
+ /**
* Starts watching for camera access by uids running on a virtual device, if we were not
* already doing so.
*/
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index cdd54719e15f..db163dcae395 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -110,6 +110,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
private final int mDeviceId;
private final InputController mInputController;
private final SensorController mSensorController;
+ private final CameraAccessController mCameraAccessController;
private VirtualAudioController mVirtualAudioController;
@VisibleForTesting
final Set<Integer> mVirtualDisplayIds = new ArraySet<>();
@@ -165,6 +166,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
IBinder token,
int ownerUid,
int deviceId,
+ CameraAccessController cameraAccessController,
OnDeviceCloseListener onDeviceCloseListener,
PendingTrampolineCallback pendingTrampolineCallback,
IVirtualDeviceActivityListener activityListener,
@@ -178,6 +180,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
deviceId,
/* inputController= */ null,
/* sensorController= */ null,
+ cameraAccessController,
onDeviceCloseListener,
pendingTrampolineCallback,
activityListener,
@@ -194,6 +197,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
int deviceId,
InputController inputController,
SensorController sensorController,
+ CameraAccessController cameraAccessController,
OnDeviceCloseListener onDeviceCloseListener,
PendingTrampolineCallback pendingTrampolineCallback,
IVirtualDeviceActivityListener activityListener,
@@ -223,6 +227,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
} else {
mSensorController = sensorController;
}
+ mCameraAccessController = cameraAccessController;
+ mCameraAccessController.startObservingIfNeeded();
mOnDeviceCloseListener = onDeviceCloseListener;
try {
token.linkToDeath(this, 0);
@@ -243,6 +249,11 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
return flags;
}
+ /** Returns the camera access controller of this device. */
+ CameraAccessController getCameraAccessController() {
+ return mCameraAccessController;
+ }
+
/** Returns the device display name. */
CharSequence getDisplayName() {
return mAssociationInfo.getDisplayName();
@@ -359,6 +370,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
mOnDeviceCloseListener.onClose(mDeviceId);
mAppToken.unlinkToDeath(this, 0);
+ mCameraAccessController.stopObservingIfNeeded();
final long ident = Binder.clearCallingIdentity();
try {
@@ -376,6 +388,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
@Override
public void onRunningAppsChanged(ArraySet<Integer> runningUids) {
+ mCameraAccessController.blockCameraAccessIfNeeded(runningUids);
mRunningAppsChangedCallback.accept(runningUids);
}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index d31729872071..758345f716c3 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -27,7 +27,6 @@ import android.annotation.SuppressLint;
import android.app.ActivityOptions;
import android.companion.AssociationInfo;
import android.companion.CompanionDeviceManager;
-import android.companion.CompanionDeviceManager.OnAssociationsChangedListener;
import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.IVirtualDeviceActivityListener;
import android.companion.virtual.IVirtualDeviceManager;
@@ -70,6 +69,7 @@ import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
@SuppressLint("LongLogTag")
@@ -87,20 +87,6 @@ public class VirtualDeviceManagerService extends SystemService {
VirtualDeviceManager.DEVICE_ID_DEFAULT + 1);
/**
- * Mapping from user IDs to CameraAccessControllers.
- */
- @GuardedBy("mVirtualDeviceManagerLock")
- private final SparseArray<CameraAccessController> mCameraAccessControllers =
- new SparseArray<>();
-
- /**
- * Mapping from device IDs to CameraAccessControllers.
- */
- @GuardedBy("mVirtualDeviceManagerLock")
- private final SparseArray<CameraAccessController> mCameraAccessControllersByDeviceId =
- new SparseArray<>();
-
- /**
* Mapping from device IDs to virtual devices.
*/
@GuardedBy("mVirtualDeviceManagerLock")
@@ -112,21 +98,6 @@ public class VirtualDeviceManagerService extends SystemService {
@GuardedBy("mVirtualDeviceManagerLock")
private final SparseArray<ArraySet<Integer>> mAppsOnVirtualDevices = new SparseArray<>();
- /**
- * Mapping from user ID to CDM associations. The associations come from
- * {@link CompanionDeviceManager#getAllAssociations()}, which contains associations across all
- * packages.
- */
- private final ConcurrentHashMap<Integer, List<AssociationInfo>> mAllAssociations =
- new ConcurrentHashMap<>();
-
- /**
- * Mapping from user ID to its change listener. The listeners are added when the user is
- * started and removed when the user stops.
- */
- private final SparseArray<OnAssociationsChangedListener> mOnAssociationsChangedListeners =
- new SparseArray<>();
-
public VirtualDeviceManagerService(Context context) {
super(context);
mImpl = new VirtualDeviceManagerImpl();
@@ -177,54 +148,9 @@ public class VirtualDeviceManagerService extends SystemService {
}
}
- @Override
- public void onUserStarting(@NonNull TargetUser user) {
- super.onUserStarting(user);
- Context userContext = getContext().createContextAsUser(user.getUserHandle(), 0);
- synchronized (mVirtualDeviceManagerLock) {
- final CompanionDeviceManager cdm =
- userContext.getSystemService(CompanionDeviceManager.class);
- final int userId = user.getUserIdentifier();
- mAllAssociations.put(userId, cdm.getAllAssociations());
- OnAssociationsChangedListener listener =
- associations -> mAllAssociations.put(userId, associations);
- mOnAssociationsChangedListeners.put(userId, listener);
- cdm.addOnAssociationsChangedListener(Runnable::run, listener);
- CameraAccessController cameraAccessController = new CameraAccessController(
- userContext, mLocalService, this::onCameraAccessBlocked);
- mCameraAccessControllers.put(user.getUserIdentifier(), cameraAccessController);
- }
- }
-
- @Override
- public void onUserStopping(@NonNull TargetUser user) {
- super.onUserStopping(user);
- synchronized (mVirtualDeviceManagerLock) {
- int userId = user.getUserIdentifier();
- mAllAssociations.remove(userId);
- final CompanionDeviceManager cdm = getContext().createContextAsUser(
- user.getUserHandle(), 0)
- .getSystemService(CompanionDeviceManager.class);
- OnAssociationsChangedListener listener = mOnAssociationsChangedListeners.get(userId);
- if (listener != null) {
- cdm.removeOnAssociationsChangedListener(listener);
- mOnAssociationsChangedListeners.remove(userId);
- }
- CameraAccessController cameraAccessController = mCameraAccessControllers.get(
- user.getUserIdentifier());
- if (cameraAccessController != null) {
- cameraAccessController.close();
- mCameraAccessControllers.remove(user.getUserIdentifier());
- } else {
- Slog.w(TAG, "Cannot unregister cameraAccessController for user " + user);
- }
- }
- }
-
void onCameraAccessBlocked(int appUid) {
synchronized (mVirtualDeviceManagerLock) {
- int size = mVirtualDevices.size();
- for (int i = 0; i < size; i++) {
+ for (int i = 0; i < mVirtualDevices.size(); i++) {
CharSequence deviceName = mVirtualDevices.valueAt(i).getDisplayName();
mVirtualDevices.valueAt(i).showToastWhereUidIsRunning(appUid,
getContext().getString(
@@ -235,6 +161,21 @@ public class VirtualDeviceManagerService extends SystemService {
}
}
+ CameraAccessController getCameraAccessController(UserHandle userHandle) {
+ int userId = userHandle.getIdentifier();
+ synchronized (mVirtualDeviceManagerLock) {
+ for (int i = 0; i < mVirtualDevices.size(); i++) {
+ final CameraAccessController cameraAccessController =
+ mVirtualDevices.valueAt(i).getCameraAccessController();
+ if (cameraAccessController.getUserId() == userId) {
+ return cameraAccessController;
+ }
+ }
+ }
+ Context userContext = getContext().createContextAsUser(userHandle, 0);
+ return new CameraAccessController(userContext, mLocalService, this::onCameraAccessBlocked);
+ }
+
@VisibleForTesting
VirtualDeviceManagerInternal getLocalServiceInstance() {
return mLocalService;
@@ -255,8 +196,34 @@ public class VirtualDeviceManagerService extends SystemService {
}
}
- class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub implements
- VirtualDeviceImpl.PendingTrampolineCallback {
+ @VisibleForTesting
+ void removeVirtualDevice(int deviceId) {
+ synchronized (mVirtualDeviceManagerLock) {
+ mAppsOnVirtualDevices.remove(deviceId);
+ mVirtualDevices.remove(deviceId);
+ }
+ }
+
+ class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub {
+
+ private final VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback =
+ new VirtualDeviceImpl.PendingTrampolineCallback() {
+ @Override
+ public void startWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline) {
+ PendingTrampoline existing = mPendingTrampolines.put(
+ pendingTrampoline.mPendingIntent.getCreatorPackage(),
+ pendingTrampoline);
+ if (existing != null) {
+ existing.mResultReceiver.send(
+ VirtualDeviceManager.LAUNCH_FAILURE_NO_ACTIVITY, null);
+ }
+ }
+
+ @Override
+ public void stopWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline) {
+ mPendingTrampolines.remove(pendingTrampoline.mPendingIntent.getCreatorPackage());
+ }
+ };
@Override // Binder call
public IVirtualDevice createVirtualDevice(
@@ -279,25 +246,16 @@ public class VirtualDeviceManagerService extends SystemService {
throw new IllegalArgumentException("No association with ID " + associationId);
}
synchronized (mVirtualDeviceManagerLock) {
- final int userId = UserHandle.getUserId(callingUid);
+ final UserHandle userHandle = getCallingUserHandle();
final CameraAccessController cameraAccessController =
- mCameraAccessControllers.get(userId);
+ getCameraAccessController(userHandle);
final int deviceId = sNextUniqueIndex.getAndIncrement();
+ final Consumer<ArraySet<Integer>> runningAppsChangedCallback =
+ runningUids -> notifyRunningAppsChanged(deviceId, runningUids);
VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl(getContext(),
- associationInfo, token, callingUid, deviceId,
- /* onDeviceCloseListener= */ this::onDeviceClosed,
- this, activityListener,
- runningUids -> {
- cameraAccessController.blockCameraAccessIfNeeded(runningUids);
- notifyRunningAppsChanged(deviceId, runningUids);
- },
- params);
- if (cameraAccessController != null) {
- cameraAccessController.startObservingIfNeeded();
- mCameraAccessControllersByDeviceId.put(deviceId, cameraAccessController);
- } else {
- Slog.w(TAG, "cameraAccessController not found for user " + userId);
- }
+ associationInfo, token, callingUid, deviceId, cameraAccessController,
+ this::onDeviceClosed, mPendingTrampolineCallback, activityListener,
+ runningAppsChangedCallback, params);
mVirtualDevices.put(deviceId, virtualDevice);
return virtualDevice;
}
@@ -409,8 +367,18 @@ public class VirtualDeviceManagerService extends SystemService {
@Nullable
private AssociationInfo getAssociationInfo(String packageName, int associationId) {
- final int callingUserId = getCallingUserHandle().getIdentifier();
- final List<AssociationInfo> associations = mAllAssociations.get(callingUserId);
+ final UserHandle userHandle = getCallingUserHandle();
+ final CompanionDeviceManager cdm =
+ getContext().createContextAsUser(userHandle, 0)
+ .getSystemService(CompanionDeviceManager.class);
+ List<AssociationInfo> associations;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ associations = cdm.getAllAssociations();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ final int callingUserId = userHandle.getIdentifier();
if (associations != null) {
final int associationSize = associations.size();
for (int i = 0; i < associationSize; i++) {
@@ -427,25 +395,15 @@ public class VirtualDeviceManagerService extends SystemService {
}
private void onDeviceClosed(int deviceId) {
- synchronized (mVirtualDeviceManagerLock) {
- mVirtualDevices.remove(deviceId);
- Intent i = new Intent(VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED);
- i.putExtra(VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID, deviceId);
- i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- final long identity = Binder.clearCallingIdentity();
- try {
- getContext().sendBroadcastAsUser(i, UserHandle.ALL);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- mAppsOnVirtualDevices.remove(deviceId);
- final CameraAccessController cameraAccessController =
- mCameraAccessControllersByDeviceId.removeReturnOld(deviceId);
- if (cameraAccessController != null) {
- cameraAccessController.stopObservingIfNeeded();
- } else {
- Slog.w(TAG, "cameraAccessController not found for device Id " + deviceId);
- }
+ removeVirtualDevice(deviceId);
+ Intent i = new Intent(VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED);
+ i.putExtra(VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID, deviceId);
+ i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ getContext().sendBroadcastAsUser(i, UserHandle.ALL);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
}
@@ -474,22 +432,6 @@ public class VirtualDeviceManagerService extends SystemService {
}
}
}
-
- @Override
- public void startWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline) {
- PendingTrampoline existing = mPendingTrampolines.put(
- pendingTrampoline.mPendingIntent.getCreatorPackage(),
- pendingTrampoline);
- if (existing != null) {
- existing.mResultReceiver.send(
- VirtualDeviceManager.LAUNCH_FAILURE_NO_ACTIVITY, null);
- }
- }
-
- @Override
- public void stopWaitingForPendingTrampoline(PendingTrampoline pendingTrampoline) {
- mPendingTrampolines.remove(pendingTrampoline.mPendingIntent.getCreatorPackage());
- }
}
private final class LocalService extends VirtualDeviceManagerInternal {
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 373080a6cff5..ff6fd4b9900e 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -273,7 +273,7 @@ public class BinaryTransparencyService extends SystemService {
String[] signerDigestHexStrings = computePackageSignerSha256Digests(
packageInfo.signingInfo);
- // log to Westworld
+ // log to statsd
FrameworkStatsLog.write(FrameworkStatsLog.APEX_INFO_GATHERED,
packageInfo.packageName,
packageInfo.getLongVersionCode(),
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 7b8ca912ecf2..9bedbd0ec584 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1000,10 +1000,6 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
@Override
public void notifySubscriptionInfoChanged() {
if (VDBG) log("notifySubscriptionInfoChanged:");
- if (!checkNotifyPermission("notifySubscriptionInfoChanged()")) {
- return;
- }
-
synchronized (mRecords) {
if (!mHasNotifySubscriptionInfoChangedOccurred) {
log("notifySubscriptionInfoChanged: first invocation mRecords.size="
@@ -1030,10 +1026,6 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
@Override
public void notifyOpportunisticSubscriptionInfoChanged() {
if (VDBG) log("notifyOpptSubscriptionInfoChanged:");
- if (!checkNotifyPermission("notifyOpportunisticSubscriptionInfoChanged()")) {
- return;
- }
-
synchronized (mRecords) {
if (!mHasNotifyOpportunisticSubscriptionInfoChangedOccurred) {
log("notifyOpptSubscriptionInfoChanged: first invocation mRecords.size="
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 4ebd7146252a..bcea40e5a9db 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -54,6 +54,7 @@ import static android.os.PowerExemptionManager.REASON_INSTR_BACKGROUND_FGS_PERMI
import static android.os.PowerExemptionManager.REASON_OPT_OUT_REQUESTED;
import static android.os.PowerExemptionManager.REASON_OP_ACTIVATE_PLATFORM_VPN;
import static android.os.PowerExemptionManager.REASON_OP_ACTIVATE_VPN;
+import static android.os.PowerExemptionManager.REASON_PACKAGE_INSTALLER;
import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT;
import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI;
import static android.os.PowerExemptionManager.REASON_PROC_STATE_TOP;
@@ -194,6 +195,7 @@ import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.SomeArgs;
import com.android.internal.os.TimeoutRecord;
import com.android.internal.os.TransferPipe;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.FrameworkStatsLog;
@@ -202,6 +204,7 @@ import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.am.ActivityManagerService.ItemMatcher;
import com.android.server.am.LowMemDetector.MemFactor;
+import com.android.server.pm.KnownPackages;
import com.android.server.uri.NeededUriGrants;
import com.android.server.wm.ActivityServiceConnectionsHolder;
@@ -2382,6 +2385,13 @@ public final class ActiveServices {
.getPotentialUserAllowedExemptionReason(callerUid, packageName);
}
}
+ if (reason == REASON_DENIED) {
+ if (ArrayUtils.contains(mAm.getPackageManagerInternal().getKnownPackageNames(
+ KnownPackages.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM), packageName)) {
+ reason = REASON_PACKAGE_INSTALLER;
+ }
+ }
+
switch (reason) {
case REASON_SYSTEM_UID:
case REASON_SYSTEM_ALLOW_LISTED:
@@ -2397,6 +2407,7 @@ public final class ActiveServices {
case REASON_ACTIVE_DEVICE_ADMIN:
case REASON_ROLE_EMERGENCY:
case REASON_ALLOWLISTED_PACKAGE:
+ case REASON_PACKAGE_INSTALLER:
return PERMISSION_GRANTED;
default:
return PERMISSION_DENIED;
diff --git a/services/core/java/com/android/server/am/ActivityManagerLocal.java b/services/core/java/com/android/server/am/ActivityManagerLocal.java
index 9f2cc7f9cb44..5175a31c16b5 100644
--- a/services/core/java/com/android/server/am/ActivityManagerLocal.java
+++ b/services/core/java/com/android/server/am/ActivityManagerLocal.java
@@ -23,6 +23,7 @@ import android.annotation.SystemApi;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.os.IBinder;
import android.os.RemoteException;
/**
@@ -95,6 +96,15 @@ public interface ActivityManagerLocal {
throws RemoteException;
/**
+ * Kill an app process associated with an SDK sandbox.
+ *
+ * @param clientApplicationThreadBinder binder value of the
+ * {@link android.app.IApplicationThread} of a client app process associated with a
+ * sandbox. This is obtained using {@link Context#getIApplicationThreadBinder()}.
+ */
+ void killSdkSandboxClientAppProcess(@NonNull IBinder clientApplicationThreadBinder);
+
+ /**
* Start a foreground service delegate.
* @param options foreground service delegate options.
* @param connection a service connection served as callback to caller.
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 191460c385ad..fc6d30bf58c9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -84,7 +84,6 @@ import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
import static android.os.Process.ZYGOTE_POLICY_FLAG_SYSTEM_PROCESS;
import static android.os.Process.ZYGOTE_PROCESS;
import static android.os.Process.getTotalMemory;
-import static android.os.Process.isSdkSandboxUid;
import static android.os.Process.isThreadInProcess;
import static android.os.Process.killProcess;
import static android.os.Process.killProcessQuiet;
@@ -259,8 +258,10 @@ import android.content.pm.ProviderInfo;
import android.content.pm.ProviderInfoList;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.SharedLibraryInfo;
import android.content.pm.TestUtilityService;
import android.content.pm.UserInfo;
+import android.content.pm.VersionedPackage;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -9040,39 +9041,55 @@ public class ActivityManagerService extends IActivityManager.Stub
sb.append("Instant-App: true\n");
}
- if (isSdkSandboxUid(process.uid)) {
- final int appUid = Process.getAppUidForSdkSandboxUid(process.uid);
+ if (process.isSdkSandbox) {
+ final String clientPackage = process.sdkSandboxClientAppPackage;
try {
- String[] clientPackages = pm.getPackagesForUid(appUid);
- // In shared UID case, don't add the package information
- if (clientPackages.length == 1) {
- appendSdkSandboxClientPackageHeader(sb, clientPackages[0], callingUserId);
+ final PackageInfo pi = pm.getPackageInfo(clientPackage,
+ PackageManager.GET_SHARED_LIBRARY_FILES, callingUserId);
+ if (pi != null) {
+ appendSdkSandboxClientPackageHeader(sb, pi);
+ appendSdkSandboxLibraryHeaders(sb, pi);
+ } else {
+ Slog.e(TAG,
+ "PackageInfo is null for SDK sandbox client: " + clientPackage);
}
} catch (RemoteException e) {
- Slog.e(TAG, "Error getting packages for client app uid: " + appUid, e);
+ Slog.e(TAG,
+ "Error getting package info for SDK sandbox client: " + clientPackage,
+ e);
}
sb.append("SdkSandbox: true\n");
}
}
}
- private void appendSdkSandboxClientPackageHeader(StringBuilder sb, String pkg, int userId) {
- final IPackageManager pm = AppGlobals.getPackageManager();
- sb.append("SdkSandbox-Client-Package: ").append(pkg);
- try {
- final PackageInfo pi = pm.getPackageInfo(pkg, 0, userId);
- if (pi != null) {
- sb.append(" v").append(pi.getLongVersionCode());
- if (pi.versionName != null) {
- sb.append(" (").append(pi.versionName).append(")");
- }
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Error getting package info for SDK sandbox client: " + pkg, e);
+ private void appendSdkSandboxClientPackageHeader(StringBuilder sb,
+ PackageInfo clientPackageInfo) {
+ sb.append("SdkSandbox-Client-Package: ").append(clientPackageInfo.packageName);
+ sb.append(" v").append(clientPackageInfo.getLongVersionCode());
+ if (clientPackageInfo.versionName != null) {
+ sb.append(" (").append(clientPackageInfo.versionName).append(")");
}
sb.append("\n");
}
+ private void appendSdkSandboxLibraryHeaders(StringBuilder sb,
+ PackageInfo clientPackageInfo) {
+ final ApplicationInfo info = clientPackageInfo.applicationInfo;
+ final List<SharedLibraryInfo> sharedLibraries = info.getSharedLibraryInfos();
+ for (int j = 0, size = sharedLibraries.size(); j < size; j++) {
+ final SharedLibraryInfo sharedLibrary = sharedLibraries.get(j);
+ if (!sharedLibrary.isSdk()) {
+ continue;
+ }
+
+ sb.append("SdkSandbox-Library: ").append(sharedLibrary.getPackageName());
+ final VersionedPackage versionedPackage = sharedLibrary.getDeclaringPackage();
+ sb.append(" v").append(versionedPackage.getLongVersionCode());
+ sb.append("\n");
+ }
+ }
+
private static String processClass(ProcessRecord process) {
if (process == null || process.getPid() == MY_PID) {
return "system_server";
@@ -13953,7 +13970,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
} else {
BroadcastFilter bf = (BroadcastFilter)target;
- if (bf.requiredPermission == null) {
+ if (bf.exported && bf.requiredPermission == null) {
allProtected = false;
break;
}
@@ -16958,6 +16975,20 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@Override
+ public void killSdkSandboxClientAppProcess(IBinder clientApplicationThreadBinder) {
+ synchronized (ActivityManagerService.this) {
+ ProcessRecord r = getRecordForAppLOSP(clientApplicationThreadBinder);
+ if (r != null) {
+ r.killLocked(
+ "sdk sandbox died",
+ ApplicationExitInfo.REASON_DEPENDENCY_DIED,
+ ApplicationExitInfo.SUBREASON_SDK_SANDBOX_DIED,
+ true);
+ }
+ }
+ }
+
+ @Override
public void onUserRemoved(@UserIdInt int userId) {
// Clean up any ActivityTaskManager state (by telling it the user is stopped)
mAtmInternal.onUserStopped(userId);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0f7a72590376..78bff9524b10 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -106,6 +106,7 @@ import android.media.IAudioService;
import android.media.ICapturePresetDevicesRoleDispatcher;
import android.media.ICommunicationDeviceDispatcher;
import android.media.IDeviceVolumeBehaviorDispatcher;
+import android.media.IDevicesForAttributesCallback;
import android.media.IMuteAwaitConnectionCallback;
import android.media.IPlaybackConfigDispatcher;
import android.media.IPreferredMixerAttributesDispatcher;
@@ -3124,6 +3125,25 @@ public class AudioService extends IAudioService.Stub
return mAudioSystem.getDevicesForAttributes(attributes, forVolume);
}
+ /**
+ * @see AudioManager#addOnDevicesForAttributesChangedListener(
+ * AudioAttributes, Executor, OnDevicesForAttributesChangedListener)
+ */
+ public void addOnDevicesForAttributesChangedListener(AudioAttributes attributes,
+ IDevicesForAttributesCallback callback) {
+ mAudioSystem.addOnDevicesForAttributesChangedListener(
+ attributes, false /* forVolume */, callback);
+ }
+
+ /**
+ * @see AudioManager#removeOnDevicesForAttributesChangedListener(
+ * OnDevicesForAttributesChangedListener)
+ */
+ public void removeOnDevicesForAttributesChangedListener(
+ IDevicesForAttributesCallback callback) {
+ mAudioSystem.removeOnDevicesForAttributesChangedListener(callback);
+ }
+
// pre-condition: event.getKeyCode() is one of KeyEvent.KEYCODE_VOLUME_UP,
// KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_MUTE
public void handleVolumeKey(@NonNull KeyEvent event, boolean isOnTv,
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index 7fefc556a02f..7839ada9d5b2 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -22,10 +22,15 @@ import android.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
import android.media.AudioMixerAttributes;
import android.media.AudioSystem;
+import android.media.IDevicesForAttributesCallback;
import android.media.ISoundDose;
import android.media.ISoundDoseCallback;
import android.media.audiopolicy.AudioMix;
+import android.os.IBinder;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
import android.os.SystemClock;
+import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
@@ -64,8 +69,21 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback,
private static final boolean USE_CACHE_FOR_GETDEVICES = true;
private ConcurrentHashMap<Pair<AudioAttributes, Boolean>, ArrayList<AudioDeviceAttributes>>
+ mLastDevicesForAttr = new ConcurrentHashMap<>();
+ private ConcurrentHashMap<Pair<AudioAttributes, Boolean>, ArrayList<AudioDeviceAttributes>>
mDevicesForAttrCache;
+ private final Object mDeviceCacheLock = new Object();
private int[] mMethodCacheHit;
+ /**
+ * Map that stores all attributes + forVolume pairs that are registered for
+ * routing change callback. The key is the {@link IBinder} that corresponds
+ * to the remote callback.
+ */
+ private final ArrayMap<IBinder, List<Pair<AudioAttributes, Boolean>>> mRegisteredAttributesMap =
+ new ArrayMap<>();
+ private final RemoteCallbackList<IDevicesForAttributesCallback>
+ mDevicesForAttributesCallbacks = new RemoteCallbackList<>();
+
private static final Object sRoutingListenerLock = new Object();
@GuardedBy("sRoutingListenerLock")
private static @Nullable OnRoutingUpdatedListener sRoutingListener;
@@ -96,6 +114,34 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback,
if (listener != null) {
listener.onRoutingUpdatedFromNative();
}
+
+ synchronized (mRegisteredAttributesMap) {
+ final int nbCallbacks = mDevicesForAttributesCallbacks.beginBroadcast();
+
+ for (int i = 0; i < nbCallbacks; i++) {
+ IDevicesForAttributesCallback cb =
+ mDevicesForAttributesCallbacks.getBroadcastItem(i);
+ List<Pair<AudioAttributes, Boolean>> attrList =
+ mRegisteredAttributesMap.get(cb.asBinder());
+
+ if (attrList == null) {
+ throw new IllegalStateException("Attribute list must not be null");
+ }
+
+ for (Pair<AudioAttributes, Boolean> attr : attrList) {
+ ArrayList<AudioDeviceAttributes> devices =
+ getDevicesForAttributes(attr.first, attr.second);
+ if (!mLastDevicesForAttr.containsKey(attr)
+ || !sameDeviceList(devices, mLastDevicesForAttr.get(attr))) {
+ try {
+ cb.onDevicesForAttributesChanged(
+ attr.first, attr.second, devices);
+ } catch (RemoteException e) { }
+ }
+ }
+ }
+ mDevicesForAttributesCallbacks.finishBroadcast();
+ }
}
interface OnRoutingUpdatedListener {
@@ -109,6 +155,66 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback,
}
/**
+ * @see AudioManager#addOnDevicesForAttributesChangedListener(
+ * AudioAttributes, Executor, OnDevicesForAttributesChangedListener)
+ */
+ public void addOnDevicesForAttributesChangedListener(AudioAttributes attributes,
+ boolean forVolume, @NonNull IDevicesForAttributesCallback listener) {
+ List<Pair<AudioAttributes, Boolean>> res;
+ final Pair<AudioAttributes, Boolean> attr = new Pair(attributes, forVolume);
+ synchronized (mRegisteredAttributesMap) {
+ res = mRegisteredAttributesMap.get(listener.asBinder());
+ if (res == null) {
+ res = new ArrayList<>();
+ mRegisteredAttributesMap.put(listener.asBinder(), res);
+ mDevicesForAttributesCallbacks.register(listener);
+ }
+
+ if (!res.contains(attr)) {
+ res.add(attr);
+ }
+ }
+
+ // Make query on registration to populate cache
+ getDevicesForAttributes(attributes, forVolume);
+ }
+
+ /**
+ * @see AudioManager#removeOnDevicesForAttributesChangedListener(
+ * OnDevicesForAttributesChangedListener)
+ */
+ public void removeOnDevicesForAttributesChangedListener(
+ @NonNull IDevicesForAttributesCallback listener) {
+ synchronized (mRegisteredAttributesMap) {
+ if (!mRegisteredAttributesMap.containsKey(listener.asBinder())) {
+ Log.w(TAG, "listener to be removed is not found.");
+ return;
+ }
+ mRegisteredAttributesMap.remove(listener.asBinder());
+ mDevicesForAttributesCallbacks.unregister(listener);
+ }
+ }
+
+ /**
+ * Helper function to compare lists of {@link AudioDeviceAttributes}
+ * @return true if the two lists contains the same devices, false otherwise.
+ */
+ private boolean sameDeviceList(@NonNull List<AudioDeviceAttributes> a,
+ @NonNull List<AudioDeviceAttributes> b) {
+ for (AudioDeviceAttributes device : a) {
+ if (!b.contains(device)) {
+ return false;
+ }
+ }
+ for (AudioDeviceAttributes device : b) {
+ if (!a.contains(device)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
* Implementation of AudioSystem.VolumeRangeInitRequestCallback
*/
@Override
@@ -159,8 +265,11 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback,
if (DEBUG_CACHE) {
Log.d(TAG, "---- clearing cache ----------");
}
- if (mDevicesForAttrCache != null) {
- synchronized (mDevicesForAttrCache) {
+ synchronized (mDeviceCacheLock) {
+ if (mDevicesForAttrCache != null) {
+ // Save latest cache to determine routing updates
+ mLastDevicesForAttr.putAll(mDevicesForAttrCache);
+
mDevicesForAttrCache.clear();
}
}
@@ -189,7 +298,7 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback,
if (USE_CACHE_FOR_GETDEVICES) {
ArrayList<AudioDeviceAttributes> res;
final Pair<AudioAttributes, Boolean> key = new Pair(attributes, forVolume);
- synchronized (mDevicesForAttrCache) {
+ synchronized (mDeviceCacheLock) {
res = mDevicesForAttrCache.get(key);
if (res == null) {
res = AudioSystem.getDevicesForAttributes(attributes, forVolume);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 5e9f0e7bf5bd..110eb1ec214e 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -628,7 +628,6 @@ public final class DisplayManagerService extends SystemService {
recordTopInsetLocked(mLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY));
updateSettingsLocked();
-
updateUserDisabledHdrTypesFromSettingsLocked();
updateUserPreferredDisplayModeSettingsLocked();
}
@@ -852,6 +851,15 @@ public final class DisplayManagerService extends SystemService {
for (int i = 0; i < userDisabledHdrTypeStrings.length; i++) {
mUserDisabledHdrTypes[i] = Integer.parseInt(userDisabledHdrTypeStrings[i]);
}
+
+ if (!mAreUserDisabledHdrTypesAllowed) {
+ mLogicalDisplayMapper.forEachLocked(
+ display -> {
+ display.setUserDisabledHdrTypes(mUserDisabledHdrTypes);
+ handleLogicalDisplayChangedLocked(display);
+ });
+ }
+
} catch (NumberFormatException e) {
Slog.e(TAG, "Failed to parse USER_DISABLED_HDR_FORMATS. "
+ "Clearing the setting.", e);
@@ -879,6 +887,15 @@ public final class DisplayManagerService extends SystemService {
Settings.Global.USER_PREFERRED_RESOLUTION_WIDTH, Display.INVALID_DISPLAY_WIDTH);
Display.Mode mode = new Display.Mode(width, height, refreshRate);
mUserPreferredMode = isResolutionAndRefreshRateValid(mode) ? mode : null;
+ if (mUserPreferredMode != null) {
+ mDisplayDeviceRepo.forEachLocked((DisplayDevice device) -> {
+ device.setUserPreferredDisplayModeLocked(mode);
+ });
+ } else {
+ mLogicalDisplayMapper.forEachLocked((LogicalDisplay display) -> {
+ configurePreferredDisplayModeLocked(display);
+ });
+ }
}
private DisplayInfo getDisplayInfoForFrameRateOverride(DisplayEventReceiver.FrameRateOverride[]
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index f4eed2b47ec4..602059ad90e0 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -852,6 +852,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mAutomaticBrightnessController.stop();
}
+ if (mScreenOffBrightnessSensorController != null) {
+ mScreenOffBrightnessSensorController.stop();
+ }
+
if (mBrightnessSetting != null) {
mBrightnessSetting.unregisterListener(mBrightnessSettingListener);
}
@@ -1093,6 +1097,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mBrightnessEventRingBuffer =
new RingBuffer<>(BrightnessEvent.class, RINGBUFFER_MAX);
+ if (mScreenOffBrightnessSensorController != null) {
+ mScreenOffBrightnessSensorController.stop();
+ }
loadScreenOffBrightnessSensor();
int[] sensorValueToLux = mDisplayDeviceConfig.getScreenOffBrightnessSensorValueToLux();
if (mScreenOffBrightnessSensor != null && sensorValueToLux != null) {
@@ -2707,6 +2714,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
dumpBrightnessEvents(pw);
}
+ if (mScreenOffBrightnessSensorController != null) {
+ mScreenOffBrightnessSensorController.dump(pw);
+ }
+
if (mHbmController != null) {
mHbmController.dump(pw);
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 09136b096653..bb8132f33fc5 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -2330,6 +2330,11 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
if (mDisplayBrightnessController != null) {
mDisplayBrightnessController.dump(pw);
}
+
+ pw.println();
+ if (mDisplayStateController != null) {
+ mDisplayStateController.dumpsys(pw);
+ }
}
diff --git a/services/core/java/com/android/server/display/ScreenOffBrightnessSensorController.java b/services/core/java/com/android/server/display/ScreenOffBrightnessSensorController.java
index 6f50dac07b99..42defac5bab8 100644
--- a/services/core/java/com/android/server/display/ScreenOffBrightnessSensorController.java
+++ b/services/core/java/com/android/server/display/ScreenOffBrightnessSensorController.java
@@ -92,6 +92,10 @@ public class ScreenOffBrightnessSensorController implements SensorEventListener
}
}
+ void stop() {
+ setLightSensorEnabled(false);
+ }
+
float getAutomaticScreenBrightness() {
if (mLastSensorValue < 0 || mLastSensorValue >= mSensorValueToLux.length
|| (!mRegistered
@@ -109,7 +113,7 @@ public class ScreenOffBrightnessSensorController implements SensorEventListener
/** Dump current state */
public void dump(PrintWriter pw) {
- pw.println("ScreenOffBrightnessSensorController:");
+ pw.println("Screen Off Brightness Sensor Controller:");
IndentingPrintWriter idpw = new IndentingPrintWriter(pw);
idpw.increaseIndent();
idpw.println("registered=" + mRegistered);
diff --git a/services/core/java/com/android/server/display/state/DisplayStateController.java b/services/core/java/com/android/server/display/state/DisplayStateController.java
index 546478e480e0..b1a1c601cc0c 100644
--- a/services/core/java/com/android/server/display/state/DisplayStateController.java
+++ b/services/core/java/com/android/server/display/state/DisplayStateController.java
@@ -98,7 +98,7 @@ public class DisplayStateController {
*/
public void dumpsys(PrintWriter pw) {
pw.println();
- pw.println("DisplayPowerProximityStateController:");
+ pw.println("DisplayStateController:");
pw.println(" mPerformScreenOffTransition:" + mPerformScreenOffTransition);
IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
if (mDisplayPowerProximityStateController != null) {
diff --git a/services/core/java/com/android/server/grammaticalinflection/OWNERS b/services/core/java/com/android/server/grammaticalinflection/OWNERS
new file mode 100644
index 000000000000..5f16ba9123b7
--- /dev/null
+++ b/services/core/java/com/android/server/grammaticalinflection/OWNERS
@@ -0,0 +1,4 @@
+# Bug template url: https://b.corp.google.com/issues/new?component=1082762&template=1601534
+allenwtsu@google.com
+goldmanj@google.com
+calvinpan@google.com
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index e1e99a1cb3b7..8dc2f52c5839 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -44,6 +44,7 @@ import android.hardware.input.IInputDeviceBatteryState;
import android.hardware.input.IInputDevicesChangedListener;
import android.hardware.input.IInputManager;
import android.hardware.input.IInputSensorEventListener;
+import android.hardware.input.IKeyboardBacklightListener;
import android.hardware.input.ITabletModeChangedListener;
import android.hardware.input.InputDeviceIdentifier;
import android.hardware.input.InputManager;
@@ -2227,6 +2228,24 @@ public class InputManagerService extends IInputManager.Stub
}
@Override
+ @EnforcePermission(Manifest.permission.MONITOR_KEYBOARD_BACKLIGHT)
+ public void registerKeyboardBacklightListener(IKeyboardBacklightListener listener) {
+ super.registerKeyboardBacklightListener_enforcePermission();
+ Objects.requireNonNull(listener);
+ mKeyboardBacklightController.registerKeyboardBacklightListener(listener,
+ Binder.getCallingPid());
+ }
+
+ @Override
+ @EnforcePermission(Manifest.permission.MONITOR_KEYBOARD_BACKLIGHT)
+ public void unregisterKeyboardBacklightListener(IKeyboardBacklightListener listener) {
+ super.unregisterKeyboardBacklightListener_enforcePermission();
+ Objects.requireNonNull(listener);
+ mKeyboardBacklightController.unregisterKeyboardBacklightListener(listener,
+ Binder.getCallingPid());
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
diff --git a/services/core/java/com/android/server/input/KeyboardBacklightController.java b/services/core/java/com/android/server/input/KeyboardBacklightController.java
index b207e27b4005..77b0d4f39ae3 100644
--- a/services/core/java/com/android/server/input/KeyboardBacklightController.java
+++ b/services/core/java/com/android/server/input/KeyboardBacklightController.java
@@ -16,14 +16,19 @@
package com.android.server.input;
+import android.annotation.BinderThread;
import android.annotation.ColorInt;
import android.content.Context;
import android.graphics.Color;
+import android.hardware.input.IKeyboardBacklightListener;
+import android.hardware.input.IKeyboardBacklightState;
import android.hardware.input.InputManager;
import android.hardware.lights.Light;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.RemoteException;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
@@ -68,6 +73,11 @@ final class KeyboardBacklightController implements InputManager.InputDeviceListe
private final Handler mHandler;
private final SparseArray<Light> mKeyboardBacklights = new SparseArray<>();
+ // List of currently registered keyboard backlight listeners
+ @GuardedBy("mKeyboardBacklightListenerRecords")
+ private final SparseArray<KeyboardBacklightListenerRecord> mKeyboardBacklightListenerRecords =
+ new SparseArray<>();
+
static {
// Fixed brightness levels to avoid issues when converting back and forth from the
// device brightness range to [0-255]
@@ -129,6 +139,9 @@ final class KeyboardBacklightController implements InputManager.InputDeviceListe
Slog.d(TAG, "Changing brightness from " + currBrightness + " to " + newBrightness);
}
+ notifyKeyboardBacklightChanged(deviceId, BRIGHTNESS_LEVELS.headSet(newBrightness).size(),
+ true/* isTriggeredByKeyPress */);
+
synchronized (mDataStore) {
try {
mDataStore.setKeyboardBacklightBrightness(inputDevice.getDescriptor(),
@@ -217,6 +230,62 @@ final class KeyboardBacklightController implements InputManager.InputDeviceListe
return null;
}
+ /** Register the keyboard backlight listener for a process. */
+ @BinderThread
+ public void registerKeyboardBacklightListener(IKeyboardBacklightListener listener,
+ int pid) {
+ synchronized (mKeyboardBacklightListenerRecords) {
+ if (mKeyboardBacklightListenerRecords.get(pid) != null) {
+ throw new IllegalStateException("The calling process has already registered "
+ + "a KeyboardBacklightListener.");
+ }
+ KeyboardBacklightListenerRecord record = new KeyboardBacklightListenerRecord(pid,
+ listener);
+ try {
+ listener.asBinder().linkToDeath(record, 0);
+ } catch (RemoteException ex) {
+ throw new RuntimeException(ex);
+ }
+ mKeyboardBacklightListenerRecords.put(pid, record);
+ }
+ }
+
+ /** Unregister the keyboard backlight listener for a process. */
+ @BinderThread
+ public void unregisterKeyboardBacklightListener(IKeyboardBacklightListener listener,
+ int pid) {
+ synchronized (mKeyboardBacklightListenerRecords) {
+ KeyboardBacklightListenerRecord record = mKeyboardBacklightListenerRecords.get(pid);
+ if (record == null) {
+ throw new IllegalStateException("The calling process has no registered "
+ + "KeyboardBacklightListener.");
+ }
+ if (record.mListener != listener) {
+ throw new IllegalStateException("The calling process has a different registered "
+ + "KeyboardBacklightListener.");
+ }
+ record.mListener.asBinder().unlinkToDeath(record, 0);
+ mKeyboardBacklightListenerRecords.remove(pid);
+ }
+ }
+
+ private void notifyKeyboardBacklightChanged(int deviceId, int currentBacklightLevel,
+ boolean isTriggeredByKeyPress) {
+ synchronized (mKeyboardBacklightListenerRecords) {
+ for (int i = 0; i < mKeyboardBacklightListenerRecords.size(); i++) {
+ mKeyboardBacklightListenerRecords.valueAt(i).notifyKeyboardBacklightChanged(
+ deviceId, new KeyboardBacklightState(currentBacklightLevel),
+ isTriggeredByKeyPress);
+ }
+ }
+ }
+
+ private void onKeyboardBacklightListenerDied(int pid) {
+ synchronized (mKeyboardBacklightListenerRecords) {
+ mKeyboardBacklightListenerRecords.remove(pid);
+ }
+ }
+
void dump(PrintWriter pw) {
IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
ipw.println(TAG + ": " + mKeyboardBacklights.size() + " keyboard backlights");
@@ -227,4 +296,49 @@ final class KeyboardBacklightController implements InputManager.InputDeviceListe
}
ipw.decreaseIndent();
}
+
+ // A record of a registered Keyboard backlight listener from one process.
+ private class KeyboardBacklightListenerRecord implements IBinder.DeathRecipient {
+ public final int mPid;
+ public final IKeyboardBacklightListener mListener;
+
+ KeyboardBacklightListenerRecord(int pid, IKeyboardBacklightListener listener) {
+ mPid = pid;
+ mListener = listener;
+ }
+
+ @Override
+ public void binderDied() {
+ if (DEBUG) {
+ Slog.d(TAG, "Keyboard backlight listener for pid " + mPid + " died.");
+ }
+ onKeyboardBacklightListenerDied(mPid);
+ }
+
+ public void notifyKeyboardBacklightChanged(int deviceId, IKeyboardBacklightState state,
+ boolean isTriggeredByKeyPress) {
+ try {
+ mListener.onBrightnessChanged(deviceId, state, isTriggeredByKeyPress);
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Failed to notify process " + mPid
+ + " that keyboard backlight changed, assuming it died.", ex);
+ binderDied();
+ }
+ }
+ }
+
+ private static class KeyboardBacklightState extends IKeyboardBacklightState {
+
+ KeyboardBacklightState(int brightnessLevel) {
+ this.brightnessLevel = brightnessLevel;
+ this.maxBrightnessLevel = NUM_BRIGHTNESS_CHANGE_STEPS;
+ }
+
+ @Override
+ public String toString() {
+ return "KeyboardBacklightState{brightnessLevel=" + brightnessLevel
+ + ", maxBrightnessLevel=" + maxBrightnessLevel
+ + "}";
+ }
+ }
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index c15b538ce605..5b9a6639bff6 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -48,12 +48,12 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE;
import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
+import static android.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY;
import static com.android.server.EventLogTags.IMF_HIDE_IME;
import static com.android.server.EventLogTags.IMF_SHOW_IME;
import static com.android.server.inputmethod.InputMethodBindingController.TIME_TO_RECONNECT;
import static com.android.server.inputmethod.InputMethodUtils.isSoftInputModeStateVisibleAllowed;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_IME_VISIBILITY;
import static java.lang.annotation.RetentionPolicy.SOURCE;
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 1998af649070..cc485baef0c9 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -29,6 +29,7 @@ import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.ActivityThread;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -51,6 +52,7 @@ import android.os.Looper;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.provider.DeviceConfig;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
@@ -89,10 +91,19 @@ class MediaRouter2ServiceImpl {
// TODO: (In Android S or later) if we add callback methods for generic failures
// in MediaRouter2, remove this constant and replace the usages with the real request IDs.
private static final long DUMMY_REQUEST_ID = -1;
- private static final int PACKAGE_IMPORTANCE_FOR_DISCOVERY = IMPORTANCE_FOREGROUND_SERVICE;
private static final int DUMP_EVENTS_MAX_COUNT = 70;
+ private static final String MEDIA_BETTER_TOGETHER_NAMESPACE = "media_better_together";
+
+ private static final String KEY_SCANNING_PACKAGE_MINIMUM_IMPORTANCE =
+ "scanning_package_minimum_importance";
+
+ private static int sPackageImportanceForScanning = DeviceConfig.getInt(
+ MEDIA_BETTER_TOGETHER_NAMESPACE,
+ /* name */ KEY_SCANNING_PACKAGE_MINIMUM_IMPORTANCE,
+ /* defaultValue */ IMPORTANCE_FOREGROUND_SERVICE);
+
private final Context mContext;
private final UserManagerInternal mUserManagerInternal;
private final Object mLock = new Object();
@@ -140,7 +151,7 @@ class MediaRouter2ServiceImpl {
mContext = context;
mActivityManager = mContext.getSystemService(ActivityManager.class);
mActivityManager.addOnUidImportanceListener(mOnUidImportanceListener,
- PACKAGE_IMPORTANCE_FOR_DISCOVERY);
+ sPackageImportanceForScanning);
mPowerManager = mContext.getSystemService(PowerManager.class);
mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
@@ -149,15 +160,26 @@ class MediaRouter2ServiceImpl {
screenOnOffIntentFilter.addAction(ACTION_SCREEN_OFF);
mContext.registerReceiver(mScreenOnOffReceiver, screenOnOffIntentFilter);
+
+ DeviceConfig.addOnPropertiesChangedListener(MEDIA_BETTER_TOGETHER_NAMESPACE,
+ ActivityThread.currentApplication().getMainExecutor(),
+ this::onDeviceConfigChange);
}
// Start of methods that implement MediaRouter2 operations.
@NonNull
- public boolean verifyPackageName(@NonNull String clientPackageName) {
+ public boolean verifyPackageExists(@NonNull String clientPackageName) {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
try {
+ mContext.enforcePermission(
+ Manifest.permission.MEDIA_CONTENT_CONTROL,
+ pid,
+ uid,
+ "Must hold MEDIA_CONTENT_CONTROL permission.");
PackageManager pm = mContext.getPackageManager();
pm.getPackageInfo(clientPackageName, PackageManager.PackageInfoFlags.of(0));
return true;
@@ -169,20 +191,6 @@ class MediaRouter2ServiceImpl {
}
@NonNull
- public void enforceMediaContentControlPermission() {
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- final long token = Binder.clearCallingIdentity();
-
- try {
- mContext.enforcePermission(Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid,
- "Must hold MEDIA_CONTENT_CONTROL permission.");
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @NonNull
public List<MediaRoute2Info> getSystemRoutes() {
final int uid = Binder.getCallingUid();
final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
@@ -1386,6 +1394,12 @@ class MediaRouter2ServiceImpl {
// End of locked methods that are used by both MediaRouter2 and MediaRouter2Manager.
+ private void onDeviceConfigChange(@NonNull DeviceConfig.Properties properties) {
+ sPackageImportanceForScanning = properties.getInt(
+ /* name */ KEY_SCANNING_PACKAGE_MINIMUM_IMPORTANCE,
+ /* defaultValue */ IMPORTANCE_FOREGROUND_SERVICE);
+ }
+
static long toUniqueRequestId(int requesterId, int originalRequestId) {
return ((long) requesterId << 32) | originalRequestId;
}
@@ -1938,12 +1952,12 @@ class MediaRouter2ServiceImpl {
@NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route) {
try {
if (route.isSystemRoute() && !routerRecord.mHasModifyAudioRoutingPermission) {
- routerRecord.mRouter.requestCreateSessionByManager(uniqueRequestId,
- oldSession, mSystemProvider.getDefaultRoute());
- } else {
- routerRecord.mRouter.requestCreateSessionByManager(uniqueRequestId,
- oldSession, route);
+ // The router lacks permission to modify system routing, so we hide system
+ // route info from them.
+ route = mSystemProvider.getDefaultRoute();
}
+ routerRecord.mRouter.requestCreateSessionByManager(
+ uniqueRequestId, oldSession, route);
} catch (RemoteException ex) {
Slog.w(TAG, "getSessionHintsForCreatingSessionOnHandler: "
+ "Failed to request. Router probably died.", ex);
@@ -2563,7 +2577,7 @@ class MediaRouter2ServiceImpl {
isManagerScanning = managerRecords.stream().anyMatch(manager ->
manager.mIsScanning && service.mActivityManager
.getPackageImportance(manager.mPackageName)
- <= PACKAGE_IMPORTANCE_FOR_DISCOVERY);
+ <= sPackageImportanceForScanning);
if (isManagerScanning) {
discoveryPreferences = routerRecords.stream()
@@ -2572,7 +2586,7 @@ class MediaRouter2ServiceImpl {
} else {
discoveryPreferences = routerRecords.stream().filter(record ->
service.mActivityManager.getPackageImportance(record.mPackageName)
- <= PACKAGE_IMPORTANCE_FOR_DISCOVERY)
+ <= sPackageImportanceForScanning)
.map(record -> record.mDiscoveryPreference)
.collect(Collectors.toList());
}
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index ad82e1c786e3..3ad0e44e6ea3 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -380,14 +380,8 @@ public final class MediaRouterService extends IMediaRouterService.Stub
// Binder call
@Override
- public boolean verifyPackageName(String clientPackageName) {
- return mService2.verifyPackageName(clientPackageName);
- }
-
- // Binder call
- @Override
- public void enforceMediaContentControlPermission() {
- mService2.enforceMediaContentControlPermission();
+ public boolean verifyPackageExists(String clientPackageName) {
+ return mService2.verifyPackageExists(clientPackageName);
}
// Binder call
diff --git a/services/core/java/com/android/server/pm/AppStateHelper.java b/services/core/java/com/android/server/pm/AppStateHelper.java
index 32479ee459ff..1a2b01eb11c5 100644
--- a/services/core/java/com/android/server/pm/AppStateHelper.java
+++ b/services/core/java/com/android/server/pm/AppStateHelper.java
@@ -37,7 +37,7 @@ import com.android.internal.util.ArrayUtils;
import com.android.server.LocalServices;
import java.util.ArrayList;
-import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -159,13 +159,21 @@ public class AppStateHelper {
return false;
}
- private static boolean containsAny(Collection<String> list, Collection<String> which) {
- if (list.isEmpty()) {
- return false;
- }
- for (var element : which) {
- if (list.contains(element)) {
+ /**
+ * True if {@code arr} contains any element in {@code which}.
+ * Both {@code arr} and {@code which} must be sorted in advance.
+ */
+ private static boolean containsAny(String[] arr, List<String> which) {
+ int s1 = arr.length;
+ int s2 = which.size();
+ for (int i = 0, j = 0; i < s1 && j < s2; ) {
+ int val = arr[i].compareTo(which.get(j));
+ if (val == 0) {
return true;
+ } else if (val < 0) {
+ ++i;
+ } else {
+ ++j;
}
}
return false;
@@ -174,9 +182,9 @@ public class AppStateHelper {
private void addLibraryDependency(ArraySet<String> results, List<String> libPackageNames) {
var pmInternal = LocalServices.getService(PackageManagerInternal.class);
- var libraryNames = new ArraySet<String>();
- var staticSharedLibraryNames = new ArraySet<String>();
- var sdkLibraryNames = new ArraySet<String>();
+ var libraryNames = new ArrayList<String>();
+ var staticSharedLibraryNames = new ArrayList<String>();
+ var sdkLibraryNames = new ArrayList<String>();
for (var packageName : libPackageNames) {
var pkg = pmInternal.getAndroidPackage(packageName);
if (pkg == null) {
@@ -199,11 +207,19 @@ public class AppStateHelper {
return;
}
- pmInternal.forEachPackage(pkg -> {
- if (containsAny(pkg.getUsesLibraries(), libraryNames)
- || containsAny(pkg.getUsesOptionalLibraries(), libraryNames)
- || containsAny(pkg.getUsesStaticLibraries(), staticSharedLibraryNames)
- || containsAny(pkg.getUsesSdkLibraries(), sdkLibraryNames)) {
+ Collections.sort(libraryNames);
+ Collections.sort(sdkLibraryNames);
+ Collections.sort(staticSharedLibraryNames);
+
+ pmInternal.forEachPackageState(pkgState -> {
+ var pkg = pkgState.getPkg();
+ if (pkg == null) {
+ return;
+ }
+ if (containsAny(pkg.getUsesLibrariesSorted(), libraryNames)
+ || containsAny(pkg.getUsesOptionalLibrariesSorted(), libraryNames)
+ || containsAny(pkg.getUsesStaticLibrariesSorted(), staticSharedLibraryNames)
+ || containsAny(pkg.getUsesSdkLibrariesSorted(), sdkLibraryNames)) {
results.add(pkg.getPackageName());
}
});
diff --git a/services/core/java/com/android/server/pm/AppsFilterBase.java b/services/core/java/com/android/server/pm/AppsFilterBase.java
index 1021e07cf946..12f6a1816e3e 100644
--- a/services/core/java/com/android/server/pm/AppsFilterBase.java
+++ b/services/core/java/com/android/server/pm/AppsFilterBase.java
@@ -399,6 +399,24 @@ public abstract class AppsFilterBase implements AppsFilterSnapshot {
Slog.wtf(TAG, "No setting found for non system uid " + callingUid);
return true;
}
+
+ if (DEBUG_TRACING) {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "getAppId");
+ }
+ final int callingAppId = UserHandle.getAppId(callingUid);
+ final int targetAppId = targetPkgSetting.getAppId();
+ if (DEBUG_TRACING) {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ }
+ if (callingAppId == targetAppId
+ || callingAppId < Process.FIRST_APPLICATION_UID
+ || targetAppId < Process.FIRST_APPLICATION_UID) {
+ if (DEBUG_LOGGING) {
+ log(callingSetting, targetPkgSetting, "same app id or core app id");
+ }
+ return false;
+ }
+
final PackageStateInternal callingPkgSetting;
if (DEBUG_TRACING) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "callingSetting instanceof");
@@ -446,27 +464,6 @@ public abstract class AppsFilterBase implements AppsFilterSnapshot {
}
}
- if (DEBUG_TRACING) {
- Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "getAppId");
- }
- final int callingAppId;
- if (callingPkgSetting != null) {
- callingAppId = callingPkgSetting.getAppId();
- } else {
- // all should be the same
- callingAppId = callingSharedPkgSettings.valueAt(0).getAppId();
- }
- final int targetAppId = targetPkgSetting.getAppId();
- if (DEBUG_TRACING) {
- Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
- }
- if (callingAppId == targetAppId) {
- if (DEBUG_LOGGING) {
- log(callingSetting, targetPkgSetting, "same app id");
- }
- return false;
- }
-
try {
if (DEBUG_TRACING) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "requestsQueryAllPackages");
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index cda7503400c7..fe122f87a423 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -614,11 +614,14 @@ public final class BackgroundDexOptService {
size += getDirectorySize(path);
if (!ArrayUtils.isEmpty(info.applicationInfo.splitSourceDirs)) {
for (String splitSourceDir : info.applicationInfo.splitSourceDirs) {
- path = Paths.get(splitSourceDir).toFile();
- if (path.isFile()) {
- path = path.getParentFile();
+ File pathSplitSourceDir = Paths.get(splitSourceDir).toFile();
+ if (pathSplitSourceDir.isFile()) {
+ pathSplitSourceDir = pathSplitSourceDir.getParentFile();
}
- size += getDirectorySize(path);
+ if (path.getAbsolutePath().equals(pathSplitSourceDir.getAbsolutePath())) {
+ continue;
+ }
+ size += getDirectorySize(pathSplitSourceDir);
}
}
return size;
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageInternal.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageInternal.java
index 8d43fe7b5f27..9eca7d651dea 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageInternal.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageInternal.java
@@ -16,6 +16,8 @@
package com.android.server.pm.parsing.pkg;
+import android.annotation.NonNull;
+
import com.android.internal.content.om.OverlayConfig;
import com.android.server.pm.pkg.AndroidPackage;
@@ -31,5 +33,15 @@ import com.android.server.pm.pkg.AndroidPackage;
*/
public interface AndroidPackageInternal extends AndroidPackage,
OverlayConfig.PackageProvider.Package {
+ @NonNull
+ String[] getUsesLibrariesSorted();
+
+ @NonNull
+ String[] getUsesOptionalLibrariesSorted();
+
+ @NonNull
+ String[] getUsesSdkLibrariesSorted();
+ @NonNull
+ String[] getUsesStaticLibrariesSorted();
}
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
index e361c9332972..ed9382b42206 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
@@ -93,6 +93,7 @@ import libcore.util.EmptyArray;
import java.io.File;
import java.security.PublicKey;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
@@ -405,6 +406,15 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
private List<AndroidPackageSplit> mSplits;
@NonNull
+ private String[] mUsesLibrariesSorted;
+ @NonNull
+ private String[] mUsesOptionalLibrariesSorted;
+ @NonNull
+ private String[] mUsesSdkLibrariesSorted;
+ @NonNull
+ private String[] mUsesStaticLibrariesSorted;
+
+ @NonNull
public static PackageImpl forParsing(@NonNull String packageName, @NonNull String baseCodePath,
@NonNull String codePath, @NonNull TypedArray manifestArray, boolean isCoreApp) {
return new PackageImpl(packageName, baseCodePath, codePath, manifestArray, isCoreApp);
@@ -1379,6 +1389,19 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
@NonNull
@Override
+ public String[] getUsesLibrariesSorted() {
+ if (mUsesLibrariesSorted == null) {
+ // Note lazy-sorting here doesn't break immutability because it always
+ // return the same content. In the case of multi-threading, data race in accessing
+ // mUsesLibrariesSorted might result in unnecessary creation of sorted copies
+ // which is OK because the case is quite rare.
+ mUsesLibrariesSorted = sortLibraries(usesLibraries);
+ }
+ return mUsesLibrariesSorted;
+ }
+
+ @NonNull
+ @Override
public List<String> getUsesNativeLibraries() {
return usesNativeLibraries;
}
@@ -1391,6 +1414,15 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
@NonNull
@Override
+ public String[] getUsesOptionalLibrariesSorted() {
+ if (mUsesOptionalLibrariesSorted == null) {
+ mUsesOptionalLibrariesSorted = sortLibraries(usesOptionalLibraries);
+ }
+ return mUsesOptionalLibrariesSorted;
+ }
+
+ @NonNull
+ @Override
public List<String> getUsesOptionalNativeLibraries() {
return usesOptionalNativeLibraries;
}
@@ -1405,6 +1437,15 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
@Override
public List<String> getUsesSdkLibraries() { return usesSdkLibraries; }
+ @NonNull
+ @Override
+ public String[] getUsesSdkLibrariesSorted() {
+ if (mUsesSdkLibrariesSorted == null) {
+ mUsesSdkLibrariesSorted = sortLibraries(usesSdkLibraries);
+ }
+ return mUsesSdkLibrariesSorted;
+ }
+
@Nullable
@Override
public String[][] getUsesSdkLibrariesCertDigests() { return usesSdkLibrariesCertDigests; }
@@ -1419,6 +1460,15 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
return usesStaticLibraries;
}
+ @NonNull
+ @Override
+ public String[] getUsesStaticLibrariesSorted() {
+ if (mUsesStaticLibrariesSorted == null) {
+ mUsesStaticLibrariesSorted = sortLibraries(usesStaticLibraries);
+ }
+ return mUsesStaticLibrariesSorted;
+ }
+
@Nullable
@Override
public String[][] getUsesStaticLibrariesCertDigests() {
@@ -2650,6 +2700,16 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
return this;
}
+ private static String[] sortLibraries(List<String> libraryNames) {
+ int size = libraryNames.size();
+ if (size == 0) {
+ return EmptyArray.STRING;
+ }
+ var arr = libraryNames.toArray(EmptyArray.STRING);
+ Arrays.sort(arr);
+ return arr;
+ }
+
private void assignDerivedFields2() {
mBaseAppInfoFlags = PackageInfoUtils.appInfoFlags(this, null);
mBaseAppInfoPrivateFlags = PackageInfoUtils.appInfoPrivateFlags(this, null);
diff --git a/services/core/java/com/android/server/security/FileIntegrityService.java b/services/core/java/com/android/server/security/FileIntegrityService.java
index 5ae697315ed1..6c0e1a43f938 100644
--- a/services/core/java/com/android/server/security/FileIntegrityService.java
+++ b/services/core/java/com/android/server/security/FileIntegrityService.java
@@ -18,6 +18,7 @@ package com.android.server.security;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -59,6 +60,7 @@ import java.util.ArrayList;
* A {@link SystemService} that provides file integrity related operations.
* @hide
*/
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
public class FileIntegrityService extends SystemService {
private static final String TAG = "FileIntegrityService";
@@ -71,7 +73,10 @@ public class FileIntegrityService extends SystemService {
private final ArrayList<X509Certificate> mTrustedCertificates =
new ArrayList<X509Certificate>();
- /** Gets the instance of the service */
+ /**
+ * Gets the instance of the service.
+ * @hide
+ */
public static FileIntegrityService getService() {
return LocalServices.getService(FileIntegrityService.class);
}
@@ -139,6 +144,7 @@ public class FileIntegrityService extends SystemService {
}
};
+ /** @hide */
public FileIntegrityService(final Context context) {
super(context);
try {
@@ -149,6 +155,7 @@ public class FileIntegrityService extends SystemService {
LocalServices.addService(FileIntegrityService.class, this);
}
+ /** @hide */
@Override
public void onStart() {
loadAllCertificates();
@@ -158,6 +165,7 @@ public class FileIntegrityService extends SystemService {
/**
* Returns whether the signature over the file's fs-verity digest can be verified by one of the
* known certiticates.
+ * @hide
*/
public boolean verifyPkcs7DetachedSignature(String signaturePath, String filePath)
throws IOException {
@@ -183,6 +191,16 @@ public class FileIntegrityService extends SystemService {
return false;
}
+ /**
+ * Enables fs-verity, if supported by the filesystem.
+ * @see <a href="https://www.kernel.org/doc/html/latest/filesystems/fsverity.html">
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+ public static void setUpFsVerity(@NonNull String filePath) throws IOException {
+ VerityUtils.setUpFsverity(filePath);
+ }
+
private void loadAllCertificates() {
// A better alternative to load certificates would be to read from .fs-verity kernel
// keyring, which fsverity_init loads to during earlier boot time from the same sources
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 8613b5027d57..966329e486f9 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -1749,7 +1749,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
setExternalControl(true, vibHolder.stats);
}
if (DEBUG) {
- Slog.e(TAG, "Playing external vibration: " + vib);
+ Slog.d(TAG, "Playing external vibration: " + vib);
}
// Vibrator will start receiving data from external channels after this point.
// Report current time as the vibration start time, for debugging.
@@ -1763,7 +1763,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
if (mCurrentExternalVibration != null
&& mCurrentExternalVibration.isHoldingSameVibration(vib)) {
if (DEBUG) {
- Slog.e(TAG, "Stopping external vibration" + vib);
+ Slog.d(TAG, "Stopping external vibration: " + vib);
}
endExternalVibrateLocked(
new Vibration.EndInfo(Vibration.Status.FINISHED),
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index fcb135e3c0d4..11013e84c732 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1451,8 +1451,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
updatePictureInPictureMode(null, false);
} else {
mLastReportedMultiWindowMode = inMultiWindowMode;
- ensureActivityConfiguration(0 /* globalChanges */, PRESERVE_WINDOWS,
- false /* ignoreVisibility */);
+ ensureActivityConfiguration(0 /* globalChanges */, PRESERVE_WINDOWS);
}
}
}
@@ -3981,6 +3980,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
void finishRelaunching() {
+ mLetterboxUiController.setRelauchingAfterRequestedOrientationChanged(false);
mTaskSupervisor.getActivityMetricsLogger().notifyActivityRelaunched(this);
if (mPendingRelaunchCount > 0) {
@@ -7724,13 +7724,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
void setRequestedOrientation(int requestedOrientation) {
+ if (mLetterboxUiController.shouldIgnoreRequestedOrientation(requestedOrientation)) {
+ return;
+ }
setOrientation(requestedOrientation, this);
// 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.
if (!getMergedOverrideConfiguration().equals(
mLastReportedConfiguration.getMergedConfiguration())) {
- ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */);
+ ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */,
+ false /* ignoreVisibility */, true /* isRequestedOrientationChanged */);
}
mAtmService.getTaskChangeNotificationController().notifyActivityRequestedOrientationChanged(
@@ -9060,7 +9064,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow) {
return ensureActivityConfiguration(globalChanges, preserveWindow,
- false /* ignoreVisibility */);
+ false /* ignoreVisibility */, false /* isRequestedOrientationChanged */);
+ }
+
+ boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow,
+ boolean ignoreVisibility) {
+ return ensureActivityConfiguration(globalChanges, preserveWindow, ignoreVisibility,
+ false /* isRequestedOrientationChanged */);
}
/**
@@ -9074,11 +9084,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
* (stopped state). This is useful for the case where we know the
* activity will be visible soon and we want to ensure its configuration
* before we make it visible.
+ * @param isRequestedOrientationChanged whether this is triggered in response to an app calling
+ * {@link android.app.Activity#setRequestedOrientation}.
* @return False if the activity was relaunched and true if it wasn't relaunched because we
* can't or the app handles the specific configuration that is changing.
*/
boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow,
- boolean ignoreVisibility) {
+ boolean ignoreVisibility, boolean isRequestedOrientationChanged) {
final Task rootTask = getRootTask();
if (rootTask.mConfigWillChange) {
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Skipping config check "
@@ -9202,6 +9214,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
} else {
mRelaunchReason = RELAUNCH_REASON_NONE;
}
+ if (isRequestedOrientationChanged) {
+ mLetterboxUiController.setRelauchingAfterRequestedOrientationChanged(true);
+ }
if (mState == PAUSING) {
// A little annoying: we are waiting for this activity to finish pausing. Let's not
// do anything now, but just flag that it needs to be restarted when done pausing.
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index b4af69e14780..034f5c8128f6 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2645,9 +2645,9 @@ class ActivityStarter {
if (differentTopTask && !mAvoidMoveToFront) {
mStartActivity.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
- if (mSourceRecord == null || (mSourceRootTask.getTopNonFinishingActivity() != null
- && mSourceRootTask.getTopNonFinishingActivity().getTask()
- == mSourceRecord.getTask())) {
+ // TODO(b/264487981): Consider using BackgroundActivityStartController to determine
+ // whether to bring the launching activity to the front.
+ if (mSourceRecord == null || inTopNonFinishingTask(mSourceRecord)) {
// We really do want to push this one into the user's face, right now.
if (mLaunchTaskBehind && mSourceRecord != null) {
intentActivity.setTaskToAffiliateWith(mSourceRecord.getTask());
@@ -2706,6 +2706,20 @@ class ActivityStarter {
mRootWindowContainer.getDefaultTaskDisplayArea(), mTargetRootTask);
}
+ private boolean inTopNonFinishingTask(ActivityRecord r) {
+ if (r == null || r.getTask() == null) {
+ return false;
+ }
+
+ final Task rTask = r.getTask();
+ final Task parent = rTask.getCreatedByOrganizerTask() != null
+ ? rTask.getCreatedByOrganizerTask() : r.getRootTask();
+ final ActivityRecord topNonFinishingActivity = parent != null
+ ? parent.getTopNonFinishingActivity() : null;
+
+ return topNonFinishingActivity != null && topNonFinishingActivity.getTask() == rTask;
+ }
+
private void resumeTargetRootTaskIfNeeded() {
if (mDoResume) {
final ActivityRecord next = mTargetRootTask.topRunningActivity(
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 473a6e5ac0c5..103b9b29379b 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1451,6 +1451,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
mUserLeaving = true;
}
+ mService.deferWindowLayout();
final Transition newTransition = task.mTransitionController.isShellTransitionsEnabled()
? task.mTransitionController.isCollecting() ? null
: task.mTransitionController.createTransition(TRANSIT_TO_FRONT) : null;
@@ -1458,9 +1459,6 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
reason = reason + " findTaskToMoveToFront";
boolean reparented = false;
if (task.isResizeable() && canUseActivityOptionsLaunchBounds(options)) {
- final Rect bounds = options.getLaunchBounds();
- task.setBounds(bounds);
-
Task targetRootTask =
mRootWindowContainer.getOrCreateRootTask(null, options, task, ON_TOP);
@@ -1473,14 +1471,11 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
// task.reparent() should already placed the task on top,
// still need moveTaskToFrontLocked() below for any transition settings.
}
- if (targetRootTask.shouldResizeRootTaskWithLaunchBounds()) {
- targetRootTask.resize(bounds, !PRESERVE_WINDOWS, !DEFER_RESUME);
- } else {
- // WM resizeTask must be done after the task is moved to the correct stack,
- // because Task's setBounds() also updates dim layer's bounds, but that has
- // dependency on the root task.
- task.resize(false /* relayout */, false /* forced */);
- }
+ // The resizeTask must be done after the task is moved to the correct root task,
+ // because Task's setBounds() also updates dim layer's bounds, but that has
+ // dependency on the root task.
+ final Rect bounds = options.getLaunchBounds();
+ task.setBounds(bounds);
}
if (!reparented) {
@@ -1510,6 +1505,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
}
} finally {
mUserLeaving = false;
+ mService.continueWindowLayout();
}
}
@@ -2570,13 +2566,13 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
: null;
boolean moveHomeTaskForward = true;
synchronized (mService.mGlobalLock) {
+ final boolean isCallerRecents = mRecentTasks.isCallerRecents(callingUid);
int activityType = ACTIVITY_TYPE_UNDEFINED;
if (activityOptions != null) {
activityType = activityOptions.getLaunchActivityType();
- final int windowingMode = activityOptions.getLaunchWindowingMode();
- if (activityOptions.freezeRecentTasksReordering()
- && mService.checkPermission(MANAGE_ACTIVITY_TASKS, callingPid, callingUid)
- == PERMISSION_GRANTED) {
+ if (activityOptions.freezeRecentTasksReordering() && (isCallerRecents
+ || ActivityTaskManagerService.checkPermission(MANAGE_ACTIVITY_TASKS,
+ callingPid, callingUid) == PERMISSION_GRANTED)) {
mRecentTasks.setFreezeTaskListReordering();
}
if (activityOptions.getLaunchRootTask() != null) {
@@ -2619,7 +2615,9 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
mRootWindowContainer.startPowerModeLaunchIfNeeded(
true /* forceSend */, targetActivity);
final LaunchingState launchingState =
- mActivityMetricsLogger.notifyActivityLaunching(task.intent);
+ mActivityMetricsLogger.notifyActivityLaunching(task.intent,
+ // Recents always has a new launching state (not combinable).
+ null /* caller */, isCallerRecents ? INVALID_UID : callingUid);
try {
mService.moveTaskToFrontLocked(null /* appThread */,
null /* callingPackage */, task.mTaskId, 0, options);
diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java
index 7bd8c538351d..6b5f068b88a7 100644
--- a/services/core/java/com/android/server/wm/AppTaskImpl.java
+++ b/services/core/java/com/android/server/wm/AppTaskImpl.java
@@ -99,7 +99,7 @@ class AppTaskImpl extends IAppTask.Stub {
throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
}
return mService.getRecentTasks().createRecentTaskInfo(task,
- false /* stripExtras */, true /* getTasksAllowed */);
+ false /* stripExtras */);
} finally {
Binder.restoreCallingIdentity(origId);
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 82237bb2c483..e7a5ee7f01d9 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -85,6 +85,7 @@ import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY;
import static android.window.DisplayAreaOrganizer.FEATURE_IME;
import static android.window.DisplayAreaOrganizer.FEATURE_ROOT;
import static android.window.DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION;
@@ -140,7 +141,6 @@ import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowContainerChildProto.DISPLAY_CONTENT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_IME_VISIBILITY;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT_METHOD;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
@@ -500,7 +500,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// Accessed directly by all users.
private boolean mLayoutNeeded;
int pendingLayoutChanges;
- boolean mLayoutAndAssignWindowLayersScheduled;
/**
* Used to gate application window layout until we have sent the complete configuration.
@@ -1109,12 +1108,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
* mDisplayMetrics.densityDpi / DENSITY_DEFAULT;
isDefaultDisplay = mDisplayId == DEFAULT_DISPLAY;
mInsetsStateController = new InsetsStateController(this);
+ initializeDisplayBaseInfo();
mDisplayFrames = new DisplayFrames(mInsetsStateController.getRawInsetsState(),
mDisplayInfo, calculateDisplayCutoutForRotation(mDisplayInfo.rotation),
calculateRoundedCornersForRotation(mDisplayInfo.rotation),
calculatePrivacyIndicatorBoundsForRotation(mDisplayInfo.rotation),
calculateDisplayShapeForRotation(mDisplayInfo.rotation));
- initializeDisplayBaseInfo();
mHoldScreenWakeLock = mWmService.mPowerManager.newWakeLock(
PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE,
@@ -6024,6 +6023,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
}
+ @Nullable
ActivityRecord topRunningActivity() {
return topRunningActivity(false /* considerKeyguardState */);
}
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index cf3a6880e712..e6d8b3db4564 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -100,6 +100,8 @@ public class DisplayRotation {
private final DisplayWindowSettings mDisplayWindowSettings;
private final Context mContext;
private final Object mLock;
+ @Nullable
+ private final DisplayRotationImmersiveAppCompatPolicy mCompatPolicyForImmersiveApps;
public final boolean isDefaultDisplay;
private final boolean mSupportAutoRotation;
@@ -205,7 +207,7 @@ public class DisplayRotation {
/**
* A flag to indicate if the display rotation should be fixed to user specified rotation
- * regardless of all other states (including app requrested orientation). {@code true} the
+ * regardless of all other states (including app requested orientation). {@code true} the
* display rotation should be fixed to user specified rotation, {@code false} otherwise.
*/
private int mFixedToUserRotation = IWindowManager.FIXED_TO_USER_ROTATION_DEFAULT;
@@ -232,6 +234,7 @@ public class DisplayRotation {
mContext = context;
mLock = lock;
isDefaultDisplay = displayContent.isDefaultDisplay;
+ mCompatPolicyForImmersiveApps = initImmersiveAppCompatPolicy(service, displayContent);
mSupportAutoRotation =
mContext.getResources().getBoolean(R.bool.config_supportAutoRotation);
@@ -255,6 +258,14 @@ public class DisplayRotation {
}
}
+ @VisibleForTesting
+ @Nullable
+ DisplayRotationImmersiveAppCompatPolicy initImmersiveAppCompatPolicy(
+ WindowManagerService service, DisplayContent displayContent) {
+ return DisplayRotationImmersiveAppCompatPolicy.createIfNeeded(
+ service.mLetterboxConfiguration, this, displayContent);
+ }
+
// Change the default value to the value specified in the sysprop
// ro.bootanim.set_orientation_<display_id>. Four values are supported: ORIENTATION_0,
// ORIENTATION_90, ORIENTATION_180 and ORIENTATION_270.
@@ -1305,11 +1316,11 @@ public class DisplayRotation {
return mAllowAllRotations;
}
- private boolean isLandscapeOrSeascape(int rotation) {
+ boolean isLandscapeOrSeascape(@Surface.Rotation final int rotation) {
return rotation == mLandscapeRotation || rotation == mSeascapeRotation;
}
- private boolean isAnyPortrait(int rotation) {
+ boolean isAnyPortrait(@Surface.Rotation final int rotation) {
return rotation == mPortraitRotation || rotation == mUpsideDownRotation;
}
@@ -1348,9 +1359,16 @@ public class DisplayRotation {
return mFoldController != null && mFoldController.overrideFrozenRotation();
}
- private boolean isRotationChoicePossible(int orientation) {
- // Rotation choice is only shown when the user is in locked mode.
- if (mUserRotationMode != WindowManagerPolicy.USER_ROTATION_LOCKED) return false;
+ private boolean isRotationChoiceAllowed(@Surface.Rotation final int proposedRotation) {
+ final boolean isRotationLockEnforced = mCompatPolicyForImmersiveApps != null
+ && mCompatPolicyForImmersiveApps.isRotationLockEnforced(proposedRotation);
+
+ // Don't show rotation choice button if
+ if (!isRotationLockEnforced // not enforcing locked rotation
+ // and the screen rotation is not locked by the user.
+ && mUserRotationMode != WindowManagerPolicy.USER_ROTATION_LOCKED) {
+ return false;
+ }
// Don't show rotation choice if we are in tabletop or book modes.
if (isTabletopAutoRotateOverrideEnabled()) return false;
@@ -1402,7 +1420,7 @@ public class DisplayRotation {
}
// Ensure that some rotation choice is possible for the given orientation.
- switch (orientation) {
+ switch (mCurrentAppOrientation) {
case ActivityInfo.SCREEN_ORIENTATION_FULL_USER:
case ActivityInfo.SCREEN_ORIENTATION_USER:
case ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED:
@@ -1719,11 +1737,11 @@ public class DisplayRotation {
@Override
- public void onProposedRotationChanged(int rotation) {
+ public void onProposedRotationChanged(@Surface.Rotation int rotation) {
ProtoLog.v(WM_DEBUG_ORIENTATION, "onProposedRotationChanged, rotation=%d", rotation);
// Send interaction power boost to improve redraw performance.
mService.mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, 0);
- if (isRotationChoicePossible(mCurrentAppOrientation)) {
+ if (isRotationChoiceAllowed(rotation)) {
final boolean isValid = isValidRotationChoice(rotation);
sendProposedRotationChangeToStatusBarInternal(rotation, isValid);
} else {
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index 7266d2194779..ba0413df6325 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -287,7 +287,7 @@ final class DisplayRotationCompatPolicy {
* <li>The activity has fixed orientation but not "locked" or "nosensor" one.
* </ul>
*/
- private boolean isTreatmentEnabledForActivity(@Nullable ActivityRecord activity) {
+ boolean isTreatmentEnabledForActivity(@Nullable ActivityRecord activity) {
return activity != null && !activity.inMultiWindowMode()
&& activity.getRequestedConfigurationOrientation() != ORIENTATION_UNDEFINED
// "locked" and "nosensor" values are often used by camera apps that can't
diff --git a/services/core/java/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicy.java
new file mode 100644
index 000000000000..74494ddd9f59
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicy.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Configuration.Orientation;
+import android.view.Surface;
+import android.view.WindowInsets.Type;
+
+/**
+ * Policy to decide whether to enforce screen rotation lock for optimisation of the screen rotation
+ * user experience for immersive applications for compatibility when ignoring orientation request.
+ *
+ * <p>This is needed because immersive apps, such as games, are often not optimized for all
+ * orientations and can have a poor UX when rotated (e.g., state loss or entering size-compat mode).
+ * Additionally, some games rely on sensors for the gameplay so users can trigger such rotations
+ * accidentally when auto rotation is on.
+ */
+final class DisplayRotationImmersiveAppCompatPolicy {
+
+ @Nullable
+ static DisplayRotationImmersiveAppCompatPolicy createIfNeeded(
+ @NonNull final LetterboxConfiguration letterboxConfiguration,
+ @NonNull final DisplayRotation displayRotation,
+ @NonNull final DisplayContent displayContent) {
+ if (!letterboxConfiguration
+ .isDisplayRotationImmersiveAppCompatPolicyEnabled(/* checkDeviceConfig */ false)) {
+ return null;
+ }
+
+ return new DisplayRotationImmersiveAppCompatPolicy(
+ letterboxConfiguration, displayRotation, displayContent);
+ }
+
+ private final DisplayRotation mDisplayRotation;
+ private final LetterboxConfiguration mLetterboxConfiguration;
+ private final DisplayContent mDisplayContent;
+
+ private DisplayRotationImmersiveAppCompatPolicy(
+ @NonNull final LetterboxConfiguration letterboxConfiguration,
+ @NonNull final DisplayRotation displayRotation,
+ @NonNull final DisplayContent displayContent) {
+ mDisplayRotation = displayRotation;
+ mLetterboxConfiguration = letterboxConfiguration;
+ mDisplayContent = displayContent;
+ }
+
+ /**
+ * Decides whether it is necessary to lock screen rotation, preventing auto rotation, based on
+ * the top activity configuration and proposed screen rotation.
+ *
+ * <p>This is needed because immersive apps, such as games, are often not optimized for all
+ * orientations and can have a poor UX when rotated. Additionally, some games rely on sensors
+ * for the gameplay so users can trigger such rotations accidentally when auto rotation is on.
+ *
+ * <p>Screen rotation is locked when the following conditions are met:
+ * <ul>
+ * <li>Top activity requests to hide status and navigation bars
+ * <li>Top activity is fullscreen and in optimal orientation (without letterboxing)
+ * <li>Rotation will lead to letterboxing due to fixed orientation.
+ * <li>{@link DisplayContent#getIgnoreOrientationRequest} is {@code true}
+ * <li>This policy is enabled on the device, for details see
+ * {@link LetterboxConfiguration#isDisplayRotationImmersiveAppCompatPolicyEnabled}
+ * </ul>
+ *
+ * @param proposedRotation new proposed {@link Surface.Rotation} for the screen.
+ * @return {@code true}, if there is a need to lock screen rotation, {@code false} otherwise.
+ */
+ boolean isRotationLockEnforced(@Surface.Rotation final int proposedRotation) {
+ if (!mLetterboxConfiguration.isDisplayRotationImmersiveAppCompatPolicyEnabled(
+ /* checkDeviceConfig */ true)) {
+ return false;
+ }
+ synchronized (mDisplayContent.mWmService.mGlobalLock) {
+ return isRotationLockEnforcedLocked(proposedRotation);
+ }
+ }
+
+ private boolean isRotationLockEnforcedLocked(@Surface.Rotation final int proposedRotation) {
+ if (!mDisplayContent.getIgnoreOrientationRequest()) {
+ return false;
+ }
+
+ final ActivityRecord activityRecord = mDisplayContent.topRunningActivity();
+ if (activityRecord == null) {
+ return false;
+ }
+
+ // Don't lock screen rotation if an activity hasn't requested to hide system bars.
+ if (!hasRequestedToHideStatusAndNavBars(activityRecord)) {
+ return false;
+ }
+
+ // Don't lock screen rotation if activity is not in fullscreen. Checking windowing mode
+ // for a task rather than an activity to exclude activity embedding scenario.
+ if (activityRecord.getTask() == null
+ || activityRecord.getTask().getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
+ return false;
+ }
+
+ // Don't lock screen rotation if activity is letterboxed.
+ if (activityRecord.areBoundsLetterboxed()) {
+ return false;
+ }
+
+ if (activityRecord.getRequestedConfigurationOrientation() == ORIENTATION_UNDEFINED) {
+ return false;
+ }
+
+ // Lock screen rotation only if, after rotation the activity's orientation won't match
+ // the screen orientation, forcing the activity to enter letterbox mode after rotation.
+ return activityRecord.getRequestedConfigurationOrientation()
+ != surfaceRotationToConfigurationOrientation(proposedRotation);
+ }
+
+ /**
+ * Checks whether activity has requested to hide status and navigation bars.
+ */
+ private boolean hasRequestedToHideStatusAndNavBars(@NonNull ActivityRecord activity) {
+ WindowState mainWindow = activity.findMainWindow();
+ if (mainWindow == null) {
+ return false;
+ }
+ return (mainWindow.getRequestedVisibleTypes()
+ & (Type.statusBars() | Type.navigationBars())) == 0;
+ }
+
+ @Orientation
+ private int surfaceRotationToConfigurationOrientation(@Surface.Rotation final int rotation) {
+ if (mDisplayRotation.isAnyPortrait(rotation)) {
+ return ORIENTATION_PORTRAIT;
+ } else if (mDisplayRotation.isLandscapeOrSeascape(rotation)) {
+ return ORIENTATION_LANDSCAPE;
+ } else {
+ return ORIENTATION_UNDEFINED;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 35e1fbb61b68..1df534f21c18 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -19,7 +19,6 @@ package com.android.server.wm;
import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.view.InsetsController.ANIMATION_TYPE_HIDE;
@@ -42,7 +41,6 @@ import android.app.StatusBarManager;
import android.app.WindowConfiguration;
import android.content.ComponentName;
import android.content.res.Resources;
-import android.graphics.Rect;
import android.util.ArrayMap;
import android.util.IntArray;
import android.util.SparseArray;
@@ -276,21 +274,22 @@ class InsetsPolicy {
/**
* @see WindowState#getInsetsState()
*/
- InsetsState getInsetsForWindowMetrics(@NonNull WindowManager.LayoutParams attrs) {
- final WindowToken token = mDisplayContent.getWindowToken(attrs.token);
- if (token != null) {
- final InsetsState rotatedState = token.getFixedRotationTransformInsetsState();
- if (rotatedState != null) {
- return rotatedState;
+ void getInsetsForWindowMetrics(@Nullable WindowToken token,
+ @NonNull InsetsState outInsetsState) {
+ final InsetsState srcState = token != null && token.isFixedRotationTransforming()
+ ? token.getFixedRotationTransformInsetsState()
+ : mStateController.getRawInsetsState();
+ outInsetsState.set(srcState, true /* copySources */);
+ for (int i = mShowingTransientTypes.size() - 1; i >= 0; i--) {
+ final InsetsSource source = outInsetsState.peekSource(mShowingTransientTypes.get(i));
+ if (source != null) {
+ source.setVisible(false);
}
}
- final boolean alwaysOnTop = token != null && token.isAlwaysOnTop();
- // Always use windowing mode fullscreen when get insets for window metrics to make sure it
- // contains all insets types.
- final InsetsState originalState = enforceInsetsPolicyForTarget(attrs,
- WINDOWING_MODE_FULLSCREEN, alwaysOnTop, mStateController.getRawInsetsState());
- InsetsState state = adjustVisibilityForTransientTypes(originalState);
- return adjustInsetsForRoundedCorners(token, state, state == originalState);
+ adjustInsetsForRoundedCorners(token, outInsetsState, false /* copyState */);
+ if (token != null && token.hasSizeCompatBounds()) {
+ outInsetsState.scale(1f / token.getCompatScale());
+ }
}
/**
@@ -423,10 +422,9 @@ class InsetsPolicy {
final Task task = activityRecord != null ? activityRecord.getTask() : null;
if (task != null && !task.getWindowConfiguration().tasksAreFloating()) {
// Use task bounds to calculating rounded corners if the task is not floating.
- final Rect roundedCornerFrame = new Rect(task.getBounds());
final InsetsState state = copyState ? new InsetsState(originalState)
: originalState;
- state.setRoundedCornerFrame(roundedCornerFrame);
+ state.setRoundedCornerFrame(task.getBounds());
return state;
}
}
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 5171f5b02899..659f8d755c59 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -33,7 +33,6 @@ import static com.android.server.wm.InsetsSourceProviderProto.SEAMLESS_ROTATING;
import static com.android.server.wm.InsetsSourceProviderProto.SERVER_VISIBLE;
import static com.android.server.wm.InsetsSourceProviderProto.SOURCE;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_INSETS_CONTROL;
-import static com.android.server.wm.WindowManagerService.H.LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -508,11 +507,6 @@ abstract class InsetsSourceProvider {
return;
}
mClientVisible = clientVisible;
- if (!mDisplayContent.mLayoutAndAssignWindowLayersScheduled) {
- mDisplayContent.mLayoutAndAssignWindowLayersScheduled = true;
- mDisplayContent.mWmService.mH.obtainMessage(
- LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED, mDisplayContent).sendToTarget();
- }
updateVisibility();
}
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 9b8423327215..f916ee40d538 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -18,6 +18,7 @@ package com.android.server.wm;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -222,23 +223,40 @@ final class LetterboxConfiguration {
// See RefreshCallbackItem for context.
private boolean mIsCameraCompatRefreshCycleThroughStopEnabled = true;
- LetterboxConfiguration(Context systemUiContext) {
- this(systemUiContext, new LetterboxConfigurationPersister(systemUiContext,
- () -> readLetterboxHorizontalReachabilityPositionFromConfig(systemUiContext,
- /* forBookMode */ false),
- () -> readLetterboxVerticalReachabilityPositionFromConfig(systemUiContext,
- /* forTabletopMode */ false),
- () -> readLetterboxHorizontalReachabilityPositionFromConfig(systemUiContext,
- /* forBookMode */ true),
- () -> readLetterboxVerticalReachabilityPositionFromConfig(systemUiContext,
- /* forTabletopMode */ true)
- ));
+ // Whether should ignore app requested orientation in response to an app
+ // calling Activity#setRequestedOrientation. See
+ // LetterboxUiController#shouldIgnoreRequestedOrientation for details.
+ private final boolean mIsPolicyForIgnoringRequestedOrientationEnabled;
+
+ // Whether enabling rotation compat policy for immersive apps that prevents auto rotation
+ // into non-optimal screen orientation while in fullscreen. This is needed because immersive
+ // apps, such as games, are often not optimized for all orientations and can have a poor UX
+ // when rotated. Additionally, some games rely on sensors for the gameplay so users can trigger
+ // such rotations accidentally when auto rotation is on.
+ private final boolean mIsDisplayRotationImmersiveAppCompatPolicyEnabled;
+
+ // Flags dynamically updated with {@link android.provider.DeviceConfig}.
+ @NonNull private final LetterboxConfigurationDeviceConfig mDeviceConfig;
+
+ LetterboxConfiguration(@NonNull final Context systemUiContext) {
+ this(systemUiContext,
+ new LetterboxConfigurationPersister(systemUiContext,
+ () -> readLetterboxHorizontalReachabilityPositionFromConfig(
+ systemUiContext, /* forBookMode */ false),
+ () -> readLetterboxVerticalReachabilityPositionFromConfig(
+ systemUiContext, /* forTabletopMode */ false),
+ () -> readLetterboxHorizontalReachabilityPositionFromConfig(
+ systemUiContext, /* forBookMode */ true),
+ () -> readLetterboxVerticalReachabilityPositionFromConfig(
+ systemUiContext, /* forTabletopMode */ true)));
}
@VisibleForTesting
- LetterboxConfiguration(Context systemUiContext,
- LetterboxConfigurationPersister letterboxConfigurationPersister) {
+ LetterboxConfiguration(@NonNull final Context systemUiContext,
+ @NonNull final LetterboxConfigurationPersister letterboxConfigurationPersister) {
mContext = systemUiContext;
+ mDeviceConfig = new LetterboxConfigurationDeviceConfig(systemUiContext.getMainExecutor());
+
mFixedOrientationLetterboxAspectRatio = mContext.getResources().getFloat(
R.dimen.config_fixedOrientationLetterboxAspectRatio);
mLetterboxActivityCornersRadius = mContext.getResources().getInteger(
@@ -274,10 +292,19 @@ final class LetterboxConfiguration {
R.bool.config_letterboxIsEnabledForTranslucentActivities);
mIsCameraCompatTreatmentEnabled = mContext.getResources().getBoolean(
R.bool.config_isWindowManagerCameraCompatTreatmentEnabled);
+ mIsCompatFakeFocusEnabled = mContext.getResources().getBoolean(
+ R.bool.config_isCompatFakeFocusEnabled);
+ mIsPolicyForIgnoringRequestedOrientationEnabled = mContext.getResources().getBoolean(
+ R.bool.config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled);
+
+ mIsDisplayRotationImmersiveAppCompatPolicyEnabled = mContext.getResources().getBoolean(
+ R.bool.config_letterboxIsDisplayRotationImmersiveAppCompatPolicyEnabled);
+ mDeviceConfig.updateFlagActiveStatus(
+ /* isActive */ mIsDisplayRotationImmersiveAppCompatPolicyEnabled,
+ /* key */ KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY);
+
mLetterboxConfigurationPersister = letterboxConfigurationPersister;
mLetterboxConfigurationPersister.start();
- mIsCompatFakeFocusEnabled = mContext.getResources()
- .getBoolean(R.bool.config_isCompatFakeFocusEnabled);
}
/**
@@ -1034,6 +1061,15 @@ final class LetterboxConfiguration {
mIsCompatFakeFocusEnabled = enabled;
}
+ /**
+ * Whether should ignore app requested orientation in response to an app calling
+ * {@link android.app.Activity#setRequestedOrientation}. See {@link
+ * LetterboxUiController#shouldIgnoreRequestedOrientation} for details.
+ */
+ boolean isPolicyForIgnoringRequestedOrientationEnabled() {
+ return mIsPolicyForIgnoringRequestedOrientationEnabled;
+ }
+
/** Whether camera compatibility treatment is enabled. */
boolean isCameraCompatTreatmentEnabled(boolean checkDeviceConfig) {
return mIsCameraCompatTreatmentEnabled
@@ -1044,7 +1080,7 @@ final class LetterboxConfiguration {
// DeviceConfig.OnPropertiesChangedListener
private static boolean isCameraCompatTreatmentAllowed() {
return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
- "enable_camera_compat_treatment", false);
+ "enable_compat_camera_treatment", true);
}
/** Whether camera compatibility refresh is enabled. */
@@ -1088,4 +1124,20 @@ final class LetterboxConfiguration {
mIsCameraCompatRefreshCycleThroughStopEnabled = true;
}
+ /**
+ * Checks whether rotation compat policy for immersive apps that prevents auto rotation
+ * into non-optimal screen orientation while in fullscreen is enabled.
+ *
+ * <p>This is needed because immersive apps, such as games, are often not optimized for all
+ * orientations and can have a poor UX when rotated. Additionally, some games rely on sensors
+ * for the gameplay so users can trigger such rotations accidentally when auto rotation is on.
+ *
+ * @param checkDeviceConfig whether should check both static config and a dynamic property
+ * from {@link DeviceConfig} or only static value.
+ */
+ boolean isDisplayRotationImmersiveAppCompatPolicyEnabled(final boolean checkDeviceConfig) {
+ return mIsDisplayRotationImmersiveAppCompatPolicyEnabled && (!checkDeviceConfig
+ || mDeviceConfig.getFlag(KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY));
+ }
+
}
diff --git a/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java b/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java
new file mode 100644
index 000000000000..cf123a1f9ace
--- /dev/null
+++ b/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.annotation.NonNull;
+import android.provider.DeviceConfig;
+import android.util.ArraySet;
+
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * Utility class that caches {@link DeviceConfig} flags for app compat features and listens
+ * to updates by implementing {@link DeviceConfig.OnPropertiesChangedListener}.
+ */
+final class LetterboxConfigurationDeviceConfig
+ implements DeviceConfig.OnPropertiesChangedListener {
+
+ static final String KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY =
+ "enable_display_rotation_immersive_app_compat_policy";
+ private static final boolean DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY =
+ true;
+
+ @VisibleForTesting
+ static final Map<String, Boolean> sKeyToDefaultValueMap = Map.of(
+ KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY,
+ DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY
+ );
+
+ // Whether enabling rotation compat policy for immersive apps that prevents auto rotation
+ // into non-optimal screen orientation while in fullscreen. This is needed because immersive
+ // apps, such as games, are often not optimized for all orientations and can have a poor UX
+ // when rotated. Additionally, some games rely on sensors for the gameplay so users can trigger
+ // such rotations accidentally when auto rotation is on.
+ private boolean mIsDisplayRotationImmersiveAppCompatPolicyEnabled =
+ DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY;
+
+ // Set of active device configs that need to be updated in
+ // DeviceConfig.OnPropertiesChangedListener#onPropertiesChanged.
+ private final ArraySet<String> mActiveDeviceConfigsSet = new ArraySet<>();
+
+ LetterboxConfigurationDeviceConfig(@NonNull final Executor executor) {
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+ executor, /* onPropertiesChangedListener */ this);
+ }
+
+ @Override
+ public void onPropertiesChanged(@NonNull final DeviceConfig.Properties properties) {
+ for (int i = mActiveDeviceConfigsSet.size() - 1; i >= 0; i--) {
+ String key = mActiveDeviceConfigsSet.valueAt(i);
+ // Reads the new configuration, if the device config properties contain the key.
+ if (properties.getKeyset().contains(key)) {
+ readAndSaveValueFromDeviceConfig(key);
+ }
+ }
+ }
+
+ /**
+ * Adds {@code key} to a set of flags that can be updated from the server if
+ * {@code isActive} is {@code true} and read it's current value from {@link DeviceConfig}.
+ */
+ void updateFlagActiveStatus(boolean isActive, String key) {
+ if (!isActive) {
+ return;
+ }
+ mActiveDeviceConfigsSet.add(key);
+ readAndSaveValueFromDeviceConfig(key);
+ }
+
+ /**
+ * Returns values of the {@code key} flag.
+ *
+ * @throws AssertionError {@code key} isn't recognised.
+ */
+ boolean getFlag(String key) {
+ switch (key) {
+ case KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY:
+ return mIsDisplayRotationImmersiveAppCompatPolicyEnabled;
+ default:
+ throw new AssertionError("Unexpected flag name: " + key);
+ }
+ }
+
+ private void readAndSaveValueFromDeviceConfig(String key) {
+ Boolean defaultValue = sKeyToDefaultValueMap.get(key);
+ if (defaultValue == null) {
+ throw new AssertionError("Haven't found default value for flag: " + key);
+ }
+ switch (key) {
+ case KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY:
+ mIsDisplayRotationImmersiveAppCompatPolicyEnabled =
+ getDeviceConfig(key, defaultValue);
+ break;
+ default:
+ throw new AssertionError("Unexpected flag name: " + key);
+ }
+ }
+
+ private boolean getDeviceConfig(String key, boolean defaultValue) {
+ return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+ key, defaultValue);
+ }
+}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index fd7e082beed4..0c8a6453e6fb 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -17,10 +17,13 @@
package com.android.server.wm;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.pm.ActivityInfo.screenOrientationToString;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__CENTER;
@@ -55,6 +58,8 @@ import static com.android.server.wm.LetterboxConfiguration.letterboxBackgroundTy
import android.annotation.Nullable;
import android.app.ActivityManager.TaskDescription;
+import android.content.pm.ActivityInfo.ScreenOrientation;
+import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
@@ -74,6 +79,7 @@ import com.android.internal.statusbar.LetterboxDetails;
import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType;
import java.io.PrintWriter;
+import java.util.function.BooleanSupplier;
/** Controls behaviour of the letterbox UI for {@link mActivityRecord}. */
// TODO(b/185262487): Improve test coverage of this class. Parts of it are tested in
@@ -131,12 +137,37 @@ final class LetterboxUiController {
// DisplayRotationCompatPolicy.
private boolean mIsRefreshAfterRotationRequested;
+ @Nullable
+ private final Boolean mBooleanPropertyIgnoreRequestedOrientation;
+
+ private boolean mIsRelauchingAfterRequestedOrientationChanged;
+
LetterboxUiController(WindowManagerService wmService, ActivityRecord activityRecord) {
mLetterboxConfiguration = wmService.mLetterboxConfiguration;
// Given activityRecord may not be fully constructed since LetterboxUiController
// is created in its constructor. It shouldn't be used in this constructor but it's safe
// to use it after since controller is only used in ActivityRecord.
mActivityRecord = activityRecord;
+
+ PackageManager packageManager = wmService.mContext.getPackageManager();
+ mBooleanPropertyIgnoreRequestedOrientation =
+ readComponentProperty(packageManager, mActivityRecord.packageName,
+ mLetterboxConfiguration::isPolicyForIgnoringRequestedOrientationEnabled,
+ PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION);
+ }
+
+ @Nullable
+ private static Boolean readComponentProperty(PackageManager packageManager, String packageName,
+ BooleanSupplier gatingCondition, String propertyName) {
+ if (!gatingCondition.getAsBoolean()) {
+ return null;
+ }
+ try {
+ return packageManager.getProperty(propertyName, packageName).getBoolean();
+ } catch (PackageManager.NameNotFoundException e) {
+ // No such property name.
+ }
+ return null;
}
/** Cleans up {@link Letterbox} if it exists.*/
@@ -154,6 +185,72 @@ final class LetterboxUiController {
}
/**
+ * Whether should ignore app requested orientation in response to an app
+ * calling {@link android.app.Activity#setRequestedOrientation}.
+ *
+ * <p>This is needed to avoid getting into {@link android.app.Activity#setRequestedOrientation}
+ * loop when {@link DisplayContent#getIgnoreOrientationRequest} is enabled or device has
+ * landscape natural orientation which app developers don't expect. For example, the loop can
+ * look like this:
+ * <ol>
+ * <li>App sets default orientation to "unspecified" at runtime
+ * <li>App requests to "portrait" after checking some condition (e.g. display rotation).
+ * <li>(2) leads to fullscreen -> letterboxed bounds change and activity relaunch because
+ * app can't handle the corresponding config changes.
+ * <li>Loop goes back to (1)
+ * </ol>
+ *
+ * <p>This treatment is enabled when the following conditions are met:
+ * <ul>
+ * <li>Flag gating the treatment is enabled
+ * <li>Opt-out component property isn't enabled
+ * <li>Opt-in component property or per-app override are enabled
+ * <li>Activity is relaunched after {@link android.app.Activity#setRequestedOrientation}
+ * call from an app or camera compat force rotation treatment is active for the activity.
+ * </ul>
+ */
+ boolean shouldIgnoreRequestedOrientation(@ScreenOrientation int requestedOrientation) {
+ if (!mLetterboxConfiguration.isPolicyForIgnoringRequestedOrientationEnabled()) {
+ return false;
+ }
+ if (Boolean.FALSE.equals(mBooleanPropertyIgnoreRequestedOrientation)) {
+ return false;
+ }
+ if (!Boolean.TRUE.equals(mBooleanPropertyIgnoreRequestedOrientation)
+ && !mActivityRecord.info.isChangeEnabled(
+ OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION)) {
+ return false;
+ }
+ if (mIsRelauchingAfterRequestedOrientationChanged) {
+ Slog.w(TAG, "Ignoring orientation update to "
+ + screenOrientationToString(requestedOrientation)
+ + " due to relaunching after setRequestedOrientation for " + mActivityRecord);
+ return true;
+ }
+ DisplayContent displayContent = mActivityRecord.mDisplayContent;
+ if (displayContent == null) {
+ return false;
+ }
+ if (displayContent.mDisplayRotationCompatPolicy != null
+ && displayContent.mDisplayRotationCompatPolicy
+ .isTreatmentEnabledForActivity(mActivityRecord)) {
+ Slog.w(TAG, "Ignoring orientation update to "
+ + screenOrientationToString(requestedOrientation)
+ + " due to camera compat treatment for " + mActivityRecord);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Sets whether an activity is relaunching after the app has called {@link
+ * android.app.Activity#setRequestedOrientation}.
+ */
+ void setRelauchingAfterRequestedOrientationChanged(boolean isRelaunching) {
+ mIsRelauchingAfterRequestedOrientationChanged = isRelaunching;
+ }
+
+ /**
* Whether activity "refresh" was requested but not finished in {@link #activityResumedLocked}
* following the camera compat force rotation in {@link DisplayRotationCompatPolicy}.
*/
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 4be1c830f331..9e959180b7e3 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -976,7 +976,7 @@ class RecentTasks {
continue;
}
- res.add(createRecentTaskInfo(task, true /* stripExtras */, getTasksAllowed));
+ res.add(createRecentTaskInfo(task, true /* stripExtras */));
}
return res;
}
@@ -1889,8 +1889,7 @@ class RecentTasks {
/**
* Creates a new RecentTaskInfo from a Task.
*/
- ActivityManager.RecentTaskInfo createRecentTaskInfo(Task tr, boolean stripExtras,
- boolean getTasksAllowed) {
+ ActivityManager.RecentTaskInfo createRecentTaskInfo(Task tr, boolean stripExtras) {
final ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo();
// If the recent Task is detached, we consider it will be re-attached to the default
// TaskDisplayArea because we currently only support recent overview in the default TDA.
@@ -1902,9 +1901,6 @@ class RecentTasks {
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) {
diff --git a/services/core/java/com/android/server/wm/RunningTasks.java b/services/core/java/com/android/server/wm/RunningTasks.java
index 1cc1a57756e9..614b405d6745 100644
--- a/services/core/java/com/android/server/wm/RunningTasks.java
+++ b/services/core/java/com/android/server/wm/RunningTasks.java
@@ -173,10 +173,6 @@ class RunningTasks implements Consumer<Task> {
}
// Fill in some deprecated values
rti.id = rti.taskId;
-
- if (!mAllowed) {
- Task.trimIneffectiveInfo(task, rti);
- }
return rti;
}
}
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 17b463febc13..b5c82a8f46fd 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -118,7 +118,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
private float mLastReportedAnimatorScale;
private String mPackageName;
private String mRelayoutTag;
- private final InsetsSourceControl[] mDummyControls = new InsetsSourceControl[0];
+ private final InsetsSourceControl.Array mDummyControls = new InsetsSourceControl.Array();
final boolean mSetsUnrestrictedKeepClearAreas;
public Session(WindowManagerService service, IWindowSessionCallback callback) {
@@ -198,7 +198,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, @InsetsType int requestedVisibleTypes,
InputChannel outInputChannel, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls, Rect outAttachedFrame,
+ InsetsSourceControl.Array outActiveControls, Rect outAttachedFrame,
float[] outSizeCompatScale) {
return mService.addWindow(this, window, attrs, viewVisibility, displayId,
UserHandle.getUserId(mUid), requestedVisibleTypes, outInputChannel, outInsetsState,
@@ -209,7 +209,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, int userId, @InsetsType int requestedVisibleTypes,
InputChannel outInputChannel, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls, Rect outAttachedFrame,
+ InsetsSourceControl.Array outActiveControls, Rect outAttachedFrame,
float[] outSizeCompatScale) {
return mService.addWindow(this, window, attrs, viewVisibility, displayId, userId,
requestedVisibleTypes, outInputChannel, outInsetsState, outActiveControls,
@@ -246,7 +246,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
int requestedWidth, int requestedHeight, int viewFlags, int flags, int seq,
int lastSyncSeqId, ClientWindowFrames outFrames,
MergedConfiguration mergedConfiguration, SurfaceControl outSurfaceControl,
- InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,
+ InsetsState outInsetsState, InsetsSourceControl.Array outActiveControls,
Bundle outSyncSeqIdBundle) {
if (false) Slog.d(TAG_WM, ">>>>>> ENTERED relayout from "
+ Binder.getCallingPid());
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 8609e10bbe92..3b5b5a903544 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -423,9 +423,6 @@ class Task extends TaskFragment {
// This number will be assigned when we evaluate OOM scores for all visible tasks.
int mLayerRank = LAYER_RANK_INVISIBLE;
- /** Helper object used for updating override configuration. */
- private Configuration mTmpConfig = new Configuration();
-
/* Unique identifier for this task. */
final int mTaskId;
/* User for which this task was created. */
@@ -796,16 +793,11 @@ class Task extends TaskFragment {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "resizeTask_" + mTaskId);
- boolean updatedConfig = false;
- mTmpConfig.setTo(getResolvedOverrideConfiguration());
- if (setBounds(bounds) != BOUNDS_CHANGE_NONE) {
- updatedConfig = !mTmpConfig.equals(getResolvedOverrideConfiguration());
- }
// This variable holds information whether the configuration didn't change in a
// significant way and the activity was kept the way it was. If it's false, it means
// the activity had to be relaunched due to configuration change.
boolean kept = true;
- if (updatedConfig) {
+ if (setBounds(bounds, forced) != BOUNDS_CHANGE_NONE) {
final ActivityRecord r = topRunningActivityLocked();
if (r != null) {
kept = r.ensureActivityConfiguration(0 /* globalChanges */,
@@ -822,8 +814,6 @@ class Task extends TaskFragment {
}
}
}
- resize(kept, forced);
-
saveLaunchingStateIfNeeded();
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -2693,12 +2683,6 @@ class Task extends TaskFragment {
return canSpecifyOrientation() && getDisplayArea().canSpecifyOrientation(orientation);
}
- void resize(boolean relayout, boolean forced) {
- if (setBounds(getRequestedOverrideBounds(), forced) != BOUNDS_CHANGE_NONE && relayout) {
- getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
- }
- }
-
@Override
void onDisplayChanged(DisplayContent dc) {
final boolean isRootTask = isRootTask();
@@ -3418,27 +3402,6 @@ class Task extends TaskFragment {
info.isSleeping = shouldSleepActivities();
}
- /**
- * Removes the activity info if the activity belongs to a different uid, which is
- * different from the app that hosts the task.
- */
- static void trimIneffectiveInfo(Task task, TaskInfo info) {
- final ActivityRecord baseActivity = task.getActivity(r -> !r.finishing,
- false /* traverseTopToBottom */);
- final int baseActivityUid =
- baseActivity != null ? baseActivity.getUid() : task.effectiveUid;
-
- if (info.topActivityInfo != null
- && task.effectiveUid != info.topActivityInfo.applicationInfo.uid) {
- info.topActivity = null;
- info.topActivityInfo = null;
- }
-
- if (task.effectiveUid != baseActivityUid) {
- info.baseActivity = null;
- }
- }
-
@Nullable PictureInPictureParams getPictureInPictureParams() {
final Task topTask = getTopMostTask();
if (topTask == null) return null;
@@ -4771,14 +4734,6 @@ class Task extends TaskFragment {
}
}
- /**
- * Returns true if this root task should be resized to match the bounds specified by
- * {@link ActivityOptions#setLaunchBounds} when launching an activity into the root task.
- */
- boolean shouldResizeRootTaskWithLaunchBounds() {
- return inPinnedWindowingMode();
- }
-
void checkTranslucentActivityWaiting(ActivityRecord top) {
if (mTranslucentActivityWaiting != top) {
mUndrawnActivitiesBelowTopTranslucent.clear();
@@ -5603,29 +5558,6 @@ class Task extends TaskFragment {
return true;
}
- // TODO: Can only be called from special methods in ActivityTaskSupervisor.
- // Need to consolidate those calls points into this resize method so anyone can call directly.
- void resize(Rect displayedBounds, boolean preserveWindows, boolean deferResume) {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "task.resize_" + getRootTaskId());
- mAtmService.deferWindowLayout();
- try {
- // TODO: Why not just set this on the root task directly vs. on each tasks?
- // Update override configurations of all tasks in the root task.
- forAllTasks(task -> {
- if (task.isResizeable()) {
- task.setBounds(displayedBounds);
- }
- }, true /* traverseTopToBottom */);
-
- if (!deferResume) {
- ensureVisibleActivitiesConfiguration(topRunningActivity(), preserveWindows);
- }
- } finally {
- mAtmService.continueWindowLayout();
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
- }
-
boolean willActivityBeVisible(IBinder token) {
final ActivityRecord r = ActivityRecord.forTokenLocked(token);
if (r == null) {
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index b8878618b21c..dd489aa1447e 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -96,6 +96,7 @@ import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.window.ITaskFragmentOrganizer;
import android.window.ScreenCapture;
+import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentOrganizerToken;
@@ -306,6 +307,10 @@ class TaskFragment extends WindowContainer<WindowContainer> {
@Nullable
private final IBinder mFragmentToken;
+ /** The animation override params for animation running on this TaskFragment. */
+ @NonNull
+ private TaskFragmentAnimationParams mAnimationParams = TaskFragmentAnimationParams.DEFAULT;
+
/**
* The bounds of the embedded TaskFragment relative to the parent Task.
* {@code null} if it is not {@link #mIsEmbedded}
@@ -453,6 +458,15 @@ class TaskFragment extends WindowContainer<WindowContainer> {
&& organizer.asBinder().equals(mTaskFragmentOrganizer.asBinder());
}
+ void setAnimationParams(@NonNull TaskFragmentAnimationParams animationParams) {
+ mAnimationParams = animationParams;
+ }
+
+ @NonNull
+ TaskFragmentAnimationParams getAnimationParams() {
+ return mAnimationParams;
+ }
+
TaskFragment getAdjacentTaskFragment() {
return mAdjacentTaskFragment;
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 302538f31951..ad1e87933027 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -647,6 +647,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
t.setCornerRadius(targetLeash, 0);
t.setShadowRadius(targetLeash, 0);
t.setMatrix(targetLeash, 1, 0, 0, 1);
+ t.setAlpha(targetLeash, 1);
// The bounds sent to the transition is always a real bounds. This means we lose
// information about "null" bounds (inheriting from parent). Core will fix-up
// non-organized window surface bounds; however, since Core can't touch organized
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 5de143d91539..7f9e808c4c93 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -756,9 +756,7 @@ class WallpaperController {
private void updateWallpaperTokens(boolean visible) {
for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx);
- if (token.updateWallpaperWindows(visible)) {
- token.mDisplayContent.assignWindowLayers(false /* setLayoutNeeded */);
- }
+ token.updateWallpaperWindows(visible);
}
}
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 87f4ad42d8bf..210d5a5f480f 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -100,7 +100,7 @@ class WallpaperWindowToken extends WindowToken {
}
/** Returns {@code true} if visibility is changed. */
- boolean updateWallpaperWindows(boolean visible) {
+ void updateWallpaperWindows(boolean visible) {
boolean changed = false;
if (mVisibleRequested != visible) {
ProtoLog.d(WM_DEBUG_WALLPAPER, "Wallpaper token %s visible=%b",
@@ -117,7 +117,6 @@ class WallpaperWindowToken extends WindowToken {
linkFixedRotationTransform(wallpaperTarget.mToken);
}
}
- return changed;
}
final WindowState wallpaperTarget =
@@ -143,7 +142,6 @@ class WallpaperWindowToken extends WindowToken {
}
setVisible(visible);
- return changed;
}
private void setVisible(boolean visible) {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 67370524dc5e..9e94fdf957b3 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -265,14 +265,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
/**
- * Callback which is triggered while changing the parent, after setting up the surface but
- * before asking the parent to assign child layers.
- */
- interface PreAssignChildLayersCallback {
- void onPreAssignChildLayers();
- }
-
- /**
* True if this an AppWindowToken and the activity which created this was launched with
* ActivityOptions.setLaunchTaskBehind.
*
@@ -605,11 +597,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
*/
@Override
void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) {
- onParentChanged(newParent, oldParent, null);
- }
-
- void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent,
- PreAssignChildLayersCallback callback) {
super.onParentChanged(newParent, oldParent);
if (mParent == null) {
return;
@@ -627,13 +614,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
reparentSurfaceControl(getSyncTransaction(), mParent.mSurfaceControl);
}
- if (callback != null) {
- callback.onPreAssignChildLayers();
- }
-
// Either way we need to ask the parent to assign us a Z-order.
mParent.assignChildLayers();
- scheduleAnimation();
}
void createSurfaceControl(boolean force) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
index 6abb8fdfca6c..42b556f77ab6 100644
--- a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
+++ b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
@@ -56,7 +56,4 @@ public class WindowManagerDebugConfig {
static final boolean SHOW_STACK_CRAWLS = false;
static final boolean DEBUG_WINDOW_CROP = false;
static final boolean DEBUG_UNKNOWN_APP_VISIBILITY = false;
-
- // TODO(b/239501597) : Have a system property to control this flag.
- public static final boolean DEBUG_IME_VISIBILITY = false;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b83f4231bd78..60b9f4b4117e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1423,9 +1423,9 @@ public class WindowManagerService extends IWindowManager.Stub
public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
int displayId, int requestUserId, @InsetsType int requestedVisibleTypes,
InputChannel outInputChannel, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls, Rect outAttachedFrame,
+ InsetsSourceControl.Array outActiveControls, Rect outAttachedFrame,
float[] outSizeCompatScale) {
- Arrays.fill(outActiveControls, null);
+ outActiveControls.set(null);
int[] appOp = new int[1];
final boolean isRoundedCornerOverlay = (attrs.privateFlags
& PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0;
@@ -2215,10 +2215,10 @@ public class WindowManagerService extends IWindowManager.Stub
int requestedWidth, int requestedHeight, int viewVisibility, int flags, int seq,
int lastSyncSeqId, ClientWindowFrames outFrames,
MergedConfiguration outMergedConfiguration, SurfaceControl outSurfaceControl,
- InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,
+ InsetsState outInsetsState, InsetsSourceControl.Array outActiveControls,
Bundle outSyncIdBundle) {
if (outActiveControls != null) {
- Arrays.fill(outActiveControls, null);
+ outActiveControls.set(null);
}
int result = 0;
boolean configChanged = false;
@@ -2590,11 +2590,12 @@ public class WindowManagerService extends IWindowManager.Stub
return result;
}
- private void getInsetsSourceControls(WindowState win, InsetsSourceControl[] outControls) {
+ private void getInsetsSourceControls(WindowState win, InsetsSourceControl.Array outArray) {
final InsetsSourceControl[] controls =
win.getDisplayContent().getInsetsStateController().getControlsForDispatch(win);
if (controls != null) {
- final int length = Math.min(controls.length, outControls.length);
+ final int length = controls.length;
+ final InsetsSourceControl[] outControls = new InsetsSourceControl[length];
for (int i = 0; i < length; i++) {
// We will leave the critical section before returning the leash to the client,
// so we need to copy the leash to prevent others release the one that we are
@@ -2607,6 +2608,7 @@ public class WindowManagerService extends IWindowManager.Stub
outControls[i].setParcelableFlags(PARCELABLE_WRITE_RETURN_VALUE);
}
}
+ outArray.set(outControls);
}
}
@@ -5358,7 +5360,6 @@ public class WindowManagerService extends IWindowManager.Stub
public static final int ANIMATION_FAILSAFE = 60;
public static final int RECOMPUTE_FOCUS = 61;
public static final int ON_POINTER_DOWN_OUTSIDE_FOCUS = 62;
- public static final int LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED = 63;
public static final int WINDOW_STATE_BLAST_SYNC_TIMEOUT = 64;
public static final int REPARENT_TASK_TO_DEFAULT_DISPLAY = 65;
public static final int INSETS_CHANGED = 66;
@@ -5635,14 +5636,6 @@ public class WindowManagerService extends IWindowManager.Stub
}
break;
}
- case LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED: {
- synchronized (mGlobalLock) {
- final DisplayContent displayContent = (DisplayContent) msg.obj;
- displayContent.mLayoutAndAssignWindowLayersScheduled = false;
- displayContent.layoutAndAssignWindowLayersIfNeeded();
- }
- break;
- }
case WINDOW_STATE_BLAST_SYNC_TIMEOUT: {
synchronized (mGlobalLock) {
final WindowState ws = (WindowState) msg.obj;
@@ -8924,28 +8917,17 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
- public boolean getWindowInsets(WindowManager.LayoutParams attrs, int displayId,
- InsetsState outInsetsState) {
- final int uid = Binder.getCallingUid();
+ public boolean getWindowInsets(int displayId, IBinder token, InsetsState outInsetsState) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
- final DisplayContent dc = getDisplayContentOrCreate(displayId, attrs.token);
+ final DisplayContent dc = getDisplayContentOrCreate(displayId, token);
if (dc == null) {
throw new WindowManager.InvalidDisplayException("Display#" + displayId
+ "could not be found!");
}
- final WindowToken token = dc.getWindowToken(attrs.token);
- final float overrideScale = mAtmService.mCompatModePackages.getCompatScale(
- attrs.packageName, uid);
- final InsetsState state = dc.getInsetsPolicy().getInsetsForWindowMetrics(attrs);
- outInsetsState.set(state, true /* copySources */);
- if (WindowState.hasCompatScale(attrs, token, overrideScale)) {
- final float compatScale = token != null && token.hasSizeCompatBounds()
- ? token.getCompatScale() * overrideScale
- : overrideScale;
- outInsetsState.scale(1f / compatScale);
- }
+ final WindowToken winToken = dc.getWindowToken(token);
+ dc.getInsetsPolicy().getInsetsForWindowMetrics(winToken, outInsetsState);
return dc.getDisplayPolicy().areSystemBarsForcedConsumedLw();
}
} finally {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index b624e8064296..7d15902c7e41 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -21,6 +21,7 @@ import static android.app.ActivityManager.isStartResultSuccessful;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_RECT_INSETS_PROVIDER;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS;
@@ -44,6 +45,7 @@ import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_TASK_FRAGMENT_OPERATION;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_SHORTCUT;
@@ -88,7 +90,9 @@ import android.window.ITransitionMetricsReporter;
import android.window.ITransitionPlayer;
import android.window.IWindowContainerTransactionCallback;
import android.window.IWindowOrganizerController;
+import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentCreationParams;
+import android.window.TaskFragmentOperation;
import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
@@ -658,7 +662,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
}
- if (windowingMode > -1) {
+ final int prevWindowingMode = container.getWindowingMode();
+ if (windowingMode > -1 && prevWindowingMode != windowingMode) {
if (mService.isInLockTaskMode()
&& WindowConfiguration.inMultiWindowMode(windowingMode)) {
throw new UnsupportedOperationException("Not supported to set multi-window"
@@ -672,9 +677,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
return effects;
}
- final int prevMode = container.getWindowingMode();
container.setWindowingMode(windowingMode);
- if (prevMode != container.getWindowingMode()) {
+ if (prevWindowingMode != container.getWindowingMode()) {
// The activity in the container may become focusable or non-focusable due to
// windowing modes changes (such as entering or leaving pinned windowing mode),
// so also apply the lifecycle effects to this transaction.
@@ -1138,6 +1142,10 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
fragment.setCompanionTaskFragment(companion);
break;
}
+ case HIERARCHY_OP_TYPE_SET_TASK_FRAGMENT_OPERATION: {
+ effects |= applyTaskFragmentOperation(hop, errorCallbackToken, organizer);
+ break;
+ }
default: {
// The other operations may change task order so they are skipped while in lock
// task mode. The above operations are still allowed because they don't move
@@ -1270,6 +1278,47 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
return effects;
}
+ /** Applies change set through {@link WindowContainerTransaction#setTaskFragmentOperation}. */
+ private int applyTaskFragmentOperation(@NonNull WindowContainerTransaction.HierarchyOp hop,
+ @Nullable IBinder errorCallbackToken, @Nullable ITaskFragmentOrganizer organizer) {
+ final IBinder fragmentToken = hop.getContainer();
+ final TaskFragment taskFragment = mLaunchTaskFragments.get(fragmentToken);
+ final TaskFragmentOperation operation = hop.getTaskFragmentOperation();
+ if (operation == null) {
+ final Throwable exception = new IllegalArgumentException(
+ "TaskFragmentOperation must be non-null");
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment,
+ HIERARCHY_OP_TYPE_SET_TASK_FRAGMENT_OPERATION, exception);
+ return 0;
+ }
+ final int opType = operation.getOpType();
+ if (taskFragment == null || !taskFragment.isAttached()) {
+ final Throwable exception = new IllegalArgumentException(
+ "Not allowed to apply operation on invalid fragment tokens opType=" + opType);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment,
+ HIERARCHY_OP_TYPE_SET_TASK_FRAGMENT_OPERATION, exception);
+ return 0;
+ }
+
+ int effect = 0;
+ switch (opType) {
+ case OP_TYPE_SET_ANIMATION_PARAMS: {
+ final TaskFragmentAnimationParams animationParams = operation.getAnimationParams();
+ if (animationParams == null) {
+ final Throwable exception = new IllegalArgumentException(
+ "TaskFragmentAnimationParams must be non-null");
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment,
+ HIERARCHY_OP_TYPE_SET_TASK_FRAGMENT_OPERATION, exception);
+ break;
+ }
+ taskFragment.setAnimationParams(animationParams);
+ break;
+ }
+ // TODO(b/263436063): move other TaskFragment related operation here.
+ }
+ return effect;
+ }
+
/** A helper method to send minimum dimension violation error to the client. */
private void sendMinimumDimensionViolation(TaskFragment taskFragment, Point minDimensions,
IBinder errorCallbackToken, String reason) {
@@ -1698,6 +1747,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
break;
case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT:
case HIERARCHY_OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT:
+ case HIERARCHY_OP_TYPE_SET_TASK_FRAGMENT_OPERATION:
enforceTaskFragmentOrganized(func, hop.getContainer(), organizer);
break;
case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT:
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index ae31ee8f288c..59c673791db3 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -364,7 +364,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
boolean mHidden = true; // Used to determine if to show child windows.
private boolean mDragResizing;
private boolean mDragResizingChangeReported = true;
- private boolean mRedrawForSyncReported;
+ private boolean mRedrawForSyncReported = true;
/**
* Used to assosciate a given set of state changes sent from MSG_RESIZED
@@ -1276,24 +1276,15 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
* @see ActivityRecord#hasSizeCompatBounds()
*/
boolean hasCompatScale() {
- return hasCompatScale(mAttrs, mActivityRecord, mOverrideScale);
- }
-
- /**
- * @return {@code true} if the application runs in size compatibility mode.
- * @see android.content.res.CompatibilityInfo#supportsScreen
- * @see ActivityRecord#hasSizeCompatBounds()
- */
- static boolean hasCompatScale(WindowManager.LayoutParams attrs, WindowToken token,
- float overrideScale) {
- if ((attrs.privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0) {
+ if ((mAttrs.privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0) {
return true;
}
- if (attrs.type == TYPE_APPLICATION_STARTING) {
+ if (mAttrs.type == TYPE_APPLICATION_STARTING) {
// Exclude starting window because it is not displayed by the application.
return false;
}
- return token != null && token.hasSizeCompatBounds() || overrideScale != 1f;
+ return mActivityRecord != null && mActivityRecord.hasSizeCompatBounds()
+ || mOverrideScale != 1f;
}
/**
@@ -5956,8 +5947,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mSyncSeqId++;
if (getSyncMethod() == BLASTSyncEngine.METHOD_BLAST) {
mPrepareSyncSeqId = mSyncSeqId;
+ requestRedrawForSync();
+ } else if (mHasSurface && mWinAnimator.mDrawState != DRAW_PENDING) {
+ // Only need to request redraw if the window has reported draw.
+ requestRedrawForSync();
}
- requestRedrawForSync();
return true;
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index a0ba8fda906f..5ecf737ede5f 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -428,10 +428,6 @@ class WindowStateAnimator {
mShownAlpha = mAlpha;
}
- private boolean isInBlastSync() {
- return mService.useBLASTSync() && mWin.useBLASTSync();
- }
-
void prepareSurfaceLocked(SurfaceControl.Transaction t) {
final WindowState w = mWin;
if (!hasSurface()) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index e4581451288f..70a7a0227a49 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -1411,5 +1411,8 @@ class ActiveAdmin {
pw.print("mtePolicy=");
pw.println(mtePolicy);
+
+ pw.print("accountTypesWithManagementDisabled=");
+ pw.println(accountTypesWithManagementDisabled);
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 15c8c2705e6c..d796ddf7a462 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -16,9 +16,25 @@
package com.android.server.devicepolicy;
+import static android.app.admin.PolicyUpdateReason.REASON_CONFLICTING_ADMIN_POLICY;
+import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_BUNDLE_KEY;
+import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_KEY;
+import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_SET_RESULT_KEY;
+import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_TARGET_USER_ID;
+import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_UPDATE_REASON_KEY;
+import static android.app.admin.PolicyUpdatesReceiver.POLICY_SET_RESULT_FAILURE;
+import static android.app.admin.PolicyUpdatesReceiver.POLICY_SET_RESULT_SUCCESS;
+
+import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.admin.PolicyUpdatesReceiver;
+import android.app.admin.TargetUser;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
import android.os.Environment;
import android.os.UserHandle;
import android.util.AtomicFile;
@@ -39,8 +55,10 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
/**
* Class responsible for setting, resolving, and enforcing policies set by multiple management
@@ -56,7 +74,7 @@ final class DevicePolicyEngine {
/**
* Map of <userId, Map<policyKey, policyState>>
*/
- private final SparseArray<Map<String, PolicyState<?>>> mUserPolicies;
+ private final SparseArray<Map<String, PolicyState<?>>> mLocalPolicies;
/**
* Map of <policyKey, policyState>
@@ -65,7 +83,7 @@ final class DevicePolicyEngine {
DevicePolicyEngine(@NonNull Context context) {
mContext = Objects.requireNonNull(context);
- mUserPolicies = new SparseArray<>();
+ mLocalPolicies = new SparseArray<>();
mGlobalPolicies = new HashMap<>();
}
@@ -92,8 +110,28 @@ final class DevicePolicyEngine {
boolean policyChanged = policyState.setPolicy(enforcingAdmin, value);
if (policyChanged) {
- enforcePolicy(policyDefinition, policyState.getCurrentResolvedPolicy(), userId);
+ enforcePolicy(
+ policyDefinition, policyState.getCurrentResolvedPolicy(), userId);
+ sendPolicyChangedToAdmins(
+ policyState.getPoliciesSetByAdmins().keySet(),
+ enforcingAdmin,
+ policyDefinition,
+ userId == enforcingAdmin.getUserId()
+ ? TargetUser.LOCAL_USER_ID : TargetUser.PARENT_USER_ID);
+
}
+ boolean wasAdminPolicyEnforced = Objects.equals(
+ policyState.getCurrentResolvedPolicy(), value);
+ sendPolicyResultToAdmin(
+ enforcingAdmin,
+ policyDefinition,
+ wasAdminPolicyEnforced,
+ // TODO: we're always sending this for now, should properly handle errors.
+ REASON_CONFLICTING_ADMIN_POLICY,
+ userId == enforcingAdmin.getUserId()
+ ? TargetUser.LOCAL_USER_ID : TargetUser.PARENT_USER_ID);
+
+ write();
return policyChanged;
}
}
@@ -117,12 +155,28 @@ final class DevicePolicyEngine {
synchronized (mLock) {
PolicyState<V> policyState = getGlobalPolicyStateLocked(policyDefinition);
- boolean policyChanged = policyState.setPolicy(enforcingAdmin, value);
+ boolean policyChanged = policyState.setPolicy(enforcingAdmin, value);
if (policyChanged) {
enforcePolicy(policyDefinition, policyState.getCurrentResolvedPolicy(),
UserHandle.USER_ALL);
+ sendPolicyChangedToAdmins(
+ policyState.getPoliciesSetByAdmins().keySet(),
+ enforcingAdmin,
+ policyDefinition,
+ TargetUser.GLOBAL_USER_ID);
}
+ boolean wasAdminPolicyEnforced = Objects.equals(
+ policyState.getCurrentResolvedPolicy(), value);
+ sendPolicyResultToAdmin(
+ enforcingAdmin,
+ policyDefinition,
+ wasAdminPolicyEnforced,
+ // TODO: we're always sending this for now, should properly handle errors.
+ REASON_CONFLICTING_ADMIN_POLICY,
+ TargetUser.GLOBAL_USER_ID);
+
+ write();
return policyChanged;
}
}
@@ -148,8 +202,26 @@ final class DevicePolicyEngine {
boolean policyChanged = policyState.removePolicy(enforcingAdmin);
if (policyChanged) {
- enforcePolicy(policyDefinition, policyState.getCurrentResolvedPolicy(), userId);
+ enforcePolicy(
+ policyDefinition, policyState.getCurrentResolvedPolicy(), userId);
+ sendPolicyChangedToAdmins(
+ policyState.getPoliciesSetByAdmins().keySet(),
+ enforcingAdmin,
+ policyDefinition,
+ userId == enforcingAdmin.getUserId()
+ ? TargetUser.LOCAL_USER_ID : TargetUser.PARENT_USER_ID);
}
+ // for a remove policy to be enforced, it means no current policy exists
+ boolean wasAdminPolicyEnforced = policyState.getCurrentResolvedPolicy() == null;
+ sendPolicyResultToAdmin(
+ enforcingAdmin,
+ policyDefinition,
+ wasAdminPolicyEnforced,
+ // TODO: we're always sending this for now, should properly handle errors.
+ REASON_CONFLICTING_ADMIN_POLICY,
+ userId == enforcingAdmin.getUserId()
+ ? TargetUser.LOCAL_USER_ID : TargetUser.PARENT_USER_ID);
+
write();
return policyChanged;
}
@@ -176,7 +248,23 @@ final class DevicePolicyEngine {
if (policyChanged) {
enforcePolicy(policyDefinition, policyState.getCurrentResolvedPolicy(),
UserHandle.USER_ALL);
+
+ sendPolicyChangedToAdmins(
+ policyState.getPoliciesSetByAdmins().keySet(),
+ enforcingAdmin,
+ policyDefinition,
+ TargetUser.GLOBAL_USER_ID);
}
+ // for a remove policy to be enforced, it means no current policy exists
+ boolean wasAdminPolicyEnforced = policyState.getCurrentResolvedPolicy() == null;
+ sendPolicyResultToAdmin(
+ enforcingAdmin,
+ policyDefinition,
+ wasAdminPolicyEnforced,
+ // TODO: we're always sending this for now, should properly handle errors.
+ REASON_CONFLICTING_ADMIN_POLICY,
+ TargetUser.GLOBAL_USER_ID);
+
write();
return policyChanged;
}
@@ -215,19 +303,18 @@ final class DevicePolicyEngine {
+ policyDefinition.getPolicyKey() + " locally.");
}
- if (!mUserPolicies.contains(userId)) {
- mUserPolicies.put(userId, new HashMap<>());
+ if (!mLocalPolicies.contains(userId)) {
+ mLocalPolicies.put(userId, new HashMap<>());
}
- if (!mUserPolicies.get(userId).containsKey(policyDefinition.getPolicyKey())) {
- mUserPolicies.get(userId).put(
+ if (!mLocalPolicies.get(userId).containsKey(policyDefinition.getPolicyKey())) {
+ mLocalPolicies.get(userId).put(
policyDefinition.getPolicyKey(), new PolicyState<>(policyDefinition));
}
- return getPolicyState(mUserPolicies.get(userId), policyDefinition);
+ return getPolicyState(mLocalPolicies.get(userId), policyDefinition);
}
@NonNull
private <V> PolicyState<V> getGlobalPolicyStateLocked(PolicyDefinition<V> policyDefinition) {
-
if (policyDefinition.isLocalOnlyPolicy()) {
throw new IllegalArgumentException("Can't set local policy "
+ policyDefinition.getPolicyKey() + " globally.");
@@ -260,9 +347,104 @@ final class DevicePolicyEngine {
// TODO: null policyValue means remove any enforced policies, ensure callbacks handle this
// properly
policyDefinition.enforcePolicy(policyValue, mContext, userId);
- // TODO: send broadcast or call callback to notify admins of policy change
- // TODO: notify calling admin of result (e.g. success, runtime failure, policy set by
- // a different admin)
+ }
+
+ private <V> void sendPolicyResultToAdmin(
+ EnforcingAdmin admin, PolicyDefinition<V> policyDefinition, boolean success,
+ int reason, int targetUserId) {
+ Intent intent = new Intent(PolicyUpdatesReceiver.ACTION_DEVICE_POLICY_SET_RESULT);
+ intent.setPackage(admin.getPackageName());
+
+ List<ResolveInfo> receivers = mContext.getPackageManager().queryBroadcastReceiversAsUser(
+ intent,
+ PackageManager.ResolveInfoFlags.of(PackageManager.GET_RECEIVERS),
+ admin.getUserId());
+ if (receivers.isEmpty()) {
+ Log.i(TAG, "Couldn't find any receivers that handle ACTION_DEVICE_POLICY_SET_RESULT"
+ + "in package " + admin.getPackageName());
+ return;
+ }
+
+ Bundle extras = new Bundle();
+ extras.putString(EXTRA_POLICY_KEY, policyDefinition.getPolicyDefinitionKey());
+ extras.putInt(EXTRA_POLICY_TARGET_USER_ID, targetUserId);
+
+ if (policyDefinition.getCallbackArgs() != null
+ && !policyDefinition.getCallbackArgs().isEmpty()) {
+ extras.putBundle(EXTRA_POLICY_BUNDLE_KEY, policyDefinition.getCallbackArgs());
+ }
+ extras.putInt(
+ EXTRA_POLICY_SET_RESULT_KEY,
+ success ? POLICY_SET_RESULT_SUCCESS : POLICY_SET_RESULT_FAILURE);
+
+ if (!success) {
+ extras.putInt(EXTRA_POLICY_UPDATE_REASON_KEY, reason);
+ }
+
+ intent.putExtras(extras);
+ maybeSendIntentToAdminReceivers(intent, UserHandle.of(admin.getUserId()), receivers);
+ }
+
+ // TODO(b/261430877): Finalise the decision on which admins to send the updates to.
+ private <V> void sendPolicyChangedToAdmins(
+ Set<EnforcingAdmin> admins, EnforcingAdmin callingAdmin,
+ PolicyDefinition<V> policyDefinition,
+ int targetUserId) {
+ for (EnforcingAdmin admin: admins) {
+ // We're sending a separate broadcast for the calling admin with the result.
+ if (admin.equals(callingAdmin)) {
+ continue;
+ }
+ maybeSendOnPolicyChanged(
+ admin, policyDefinition, REASON_CONFLICTING_ADMIN_POLICY, targetUserId);
+ }
+ }
+
+ private <V> void maybeSendOnPolicyChanged(
+ EnforcingAdmin admin, PolicyDefinition<V> policyDefinition, int reason,
+ int targetUserId) {
+ Intent intent = new Intent(PolicyUpdatesReceiver.ACTION_DEVICE_POLICY_CHANGED);
+ intent.setPackage(admin.getPackageName());
+
+ List<ResolveInfo> receivers = mContext.getPackageManager().queryBroadcastReceiversAsUser(
+ intent,
+ PackageManager.ResolveInfoFlags.of(PackageManager.GET_RECEIVERS),
+ admin.getUserId());
+ if (receivers.isEmpty()) {
+ Log.i(TAG, "Couldn't find any receivers that handle ACTION_DEVICE_POLICY_CHANGED"
+ + "in package " + admin.getPackageName());
+ return;
+ }
+
+ Bundle extras = new Bundle();
+ extras.putString(EXTRA_POLICY_KEY, policyDefinition.getPolicyDefinitionKey());
+ extras.putInt(EXTRA_POLICY_TARGET_USER_ID, targetUserId);
+
+ if (policyDefinition.getCallbackArgs() != null
+ && !policyDefinition.getCallbackArgs().isEmpty()) {
+ extras.putBundle(EXTRA_POLICY_BUNDLE_KEY, policyDefinition.getCallbackArgs());
+ }
+ extras.putInt(EXTRA_POLICY_UPDATE_REASON_KEY, reason);
+ intent.putExtras(extras);
+ maybeSendIntentToAdminReceivers(
+ intent, UserHandle.of(admin.getUserId()), receivers);
+ }
+
+ private void maybeSendIntentToAdminReceivers(
+ Intent intent, UserHandle userHandle, List<ResolveInfo> receivers) {
+ for (ResolveInfo resolveInfo : receivers) {
+ if (!Manifest.permission.BIND_DEVICE_ADMIN.equals(
+ resolveInfo.activityInfo.permission)) {
+ Log.w(TAG, "Receiver " + resolveInfo.activityInfo + " is not protected by"
+ + "BIND_DEVICE_ADMIN permission!");
+ continue;
+ }
+ // TODO: If admins are always bound to, do I still need to set
+ // "BroadcastOptions.setBackgroundActivityStartsAllowed"?
+ // TODO: maybe protect it with a permission that is granted to the role so that we
+ // don't accidentally send a broadcast to an admin that no longer holds the role.
+ mContext.sendBroadcastAsUser(intent, userHandle);
+ }
}
private void write() {
@@ -283,14 +465,14 @@ final class DevicePolicyEngine {
private void clear() {
synchronized (mLock) {
mGlobalPolicies.clear();
- mUserPolicies.clear();
+ mLocalPolicies.clear();
}
}
private class DevicePoliciesReaderWriter {
private static final String DEVICE_POLICIES_XML = "device_policies.xml";
- private static final String TAG_USER_POLICY_ENTRY = "user-policy-entry";
- private static final String TAG_DEVICE_POLICY_ENTRY = "device-policy-entry";
+ private static final String TAG_LOCAL_POLICY_ENTRY = "local-policy-entry";
+ private static final String TAG_GLOBAL_POLICY_ENTRY = "global-policy-entry";
private static final String TAG_ADMINS_POLICY_ENTRY = "admins-policy-entry";
private static final String ATTR_USER_ID = "user-id";
private static final String ATTR_POLICY_ID = "policy-id";
@@ -332,17 +514,17 @@ final class DevicePolicyEngine {
// TODO(b/256846294): Add versioning to read/write
void writeInner(TypedXmlSerializer serializer) throws IOException {
- writeUserPoliciesInner(serializer);
- writeDevicePoliciesInner(serializer);
+ writeLocalPoliciesInner(serializer);
+ writeGlobalPoliciesInner(serializer);
}
- private void writeUserPoliciesInner(TypedXmlSerializer serializer) throws IOException {
- if (mUserPolicies != null) {
- for (int i = 0; i < mUserPolicies.size(); i++) {
- int userId = mUserPolicies.keyAt(i);
- for (Map.Entry<String, PolicyState<?>> policy : mUserPolicies.get(
+ private void writeLocalPoliciesInner(TypedXmlSerializer serializer) throws IOException {
+ if (mLocalPolicies != null) {
+ for (int i = 0; i < mLocalPolicies.size(); i++) {
+ int userId = mLocalPolicies.keyAt(i);
+ for (Map.Entry<String, PolicyState<?>> policy : mLocalPolicies.get(
userId).entrySet()) {
- serializer.startTag(/* namespace= */ null, TAG_USER_POLICY_ENTRY);
+ serializer.startTag(/* namespace= */ null, TAG_LOCAL_POLICY_ENTRY);
serializer.attributeInt(/* namespace= */ null, ATTR_USER_ID, userId);
serializer.attribute(
@@ -352,16 +534,16 @@ final class DevicePolicyEngine {
policy.getValue().saveToXml(serializer);
serializer.endTag(/* namespace= */ null, TAG_ADMINS_POLICY_ENTRY);
- serializer.endTag(/* namespace= */ null, TAG_USER_POLICY_ENTRY);
+ serializer.endTag(/* namespace= */ null, TAG_LOCAL_POLICY_ENTRY);
}
}
}
}
- private void writeDevicePoliciesInner(TypedXmlSerializer serializer) throws IOException {
+ private void writeGlobalPoliciesInner(TypedXmlSerializer serializer) throws IOException {
if (mGlobalPolicies != null) {
for (Map.Entry<String, PolicyState<?>> policy : mGlobalPolicies.entrySet()) {
- serializer.startTag(/* namespace= */ null, TAG_DEVICE_POLICY_ENTRY);
+ serializer.startTag(/* namespace= */ null, TAG_GLOBAL_POLICY_ENTRY);
serializer.attribute(/* namespace= */ null, ATTR_POLICY_ID, policy.getKey());
@@ -369,7 +551,7 @@ final class DevicePolicyEngine {
policy.getValue().saveToXml(serializer);
serializer.endTag(/* namespace= */ null, TAG_ADMINS_POLICY_ENTRY);
- serializer.endTag(/* namespace= */ null, TAG_DEVICE_POLICY_ENTRY);
+ serializer.endTag(/* namespace= */ null, TAG_GLOBAL_POLICY_ENTRY);
}
}
}
@@ -402,11 +584,11 @@ final class DevicePolicyEngine {
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
String tag = parser.getName();
switch (tag) {
- case TAG_USER_POLICY_ENTRY:
- readUserPoliciesInner(parser);
+ case TAG_LOCAL_POLICY_ENTRY:
+ readLocalPoliciesInner(parser);
break;
- case TAG_DEVICE_POLICY_ENTRY:
- readDevicePoliciesInner(parser);
+ case TAG_GLOBAL_POLICY_ENTRY:
+ readGlobalPoliciesInner(parser);
break;
default:
Log.e(TAG, "Unknown tag " + tag);
@@ -414,24 +596,24 @@ final class DevicePolicyEngine {
}
}
- private void readUserPoliciesInner(TypedXmlPullParser parser)
+ private void readLocalPoliciesInner(TypedXmlPullParser parser)
throws XmlPullParserException, IOException {
int userId = parser.getAttributeInt(/* namespace= */ null, ATTR_USER_ID);
String policyKey = parser.getAttributeValue(
/* namespace= */ null, ATTR_POLICY_ID);
- if (!mUserPolicies.contains(userId)) {
- mUserPolicies.put(userId, new HashMap<>());
+ if (!mLocalPolicies.contains(userId)) {
+ mLocalPolicies.put(userId, new HashMap<>());
}
PolicyState<?> adminsPolicy = parseAdminsPolicy(parser);
if (adminsPolicy != null) {
- mUserPolicies.get(userId).put(policyKey, adminsPolicy);
+ mLocalPolicies.get(userId).put(policyKey, adminsPolicy);
} else {
Log.e(TAG, "Error parsing file, " + policyKey + "doesn't have an "
+ "AdminsPolicy.");
}
}
- private void readDevicePoliciesInner(TypedXmlPullParser parser)
+ private void readGlobalPoliciesInner(TypedXmlPullParser parser)
throws IOException, XmlPullParserException {
String policyKey = parser.getAttributeValue(/* namespace= */ null, ATTR_POLICY_ID);
PolicyState<?> adminsPolicy = parseAdminsPolicy(parser);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 8c2065e7f764..51f3e321338d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -18,7 +18,13 @@ package com.android.server.devicepolicy;
import static android.Manifest.permission.BIND_DEVICE_ADMIN;
import static android.Manifest.permission.MANAGE_CA_CERTIFICATES;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL;
+import static android.Manifest.permission.QUERY_ADMIN_POLICY;
import static android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY;
+import static android.Manifest.permission.SET_TIME;
+import static android.Manifest.permission.SET_TIME_ZONE;
import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
import static android.app.AppOpsManager.MODE_ALLOWED;
@@ -137,6 +143,7 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK;
@@ -715,7 +722,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
+ "management app's authentication policy";
private static final String NOT_SYSTEM_CALLER_MSG = "Only the system can %s";
+ private static final String PERMISSION_BASED_ACCESS_EXPERIMENT_FLAG =
+ "enable_permission_based_access";
private static final String ENABLE_COEXISTENCE_FLAG = "enable_coexistence";
+ private static final boolean DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG = false;
private static final boolean DEFAULT_ENABLE_COEXISTENCE_FLAG = false;
// TODO(b/258425381) remove the flag after rollout.
@@ -8033,9 +8043,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
+ if (isPermissionCheckFlagEnabled()) {
+ // The effect of this policy is device-wide.
+ enforcePermission(SET_TIME, UserHandle.USER_ALL);
+ } else {
+ Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(
+ caller));
+ }
mInjector.binderWithCleanCallingIdentity(() ->
mInjector.settingsGlobalPutInt(Settings.Global.AUTO_TIME, enabled ? 1 : 0));
@@ -8057,8 +8073,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
+
+ if (isPermissionCheckFlagEnabled()) {
+ enforceCanQuery(SET_TIME, UserHandle.USER_ALL);
+ } else {
+ Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(
+ caller));
+ }
return mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) > 0;
}
@@ -8074,15 +8096,23 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
+
+ if (isPermissionCheckFlagEnabled()) {
+ // The effect of this policy is device-wide.
+ enforcePermission(SET_TIME_ZONE, UserHandle.USER_ALL);
+ } else {
+ Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(
+ caller));
+ }
if (isCoexistenceEnabled(caller)) {
mDevicePolicyEngine.setGlobalPolicy(
PolicyDefinition.AUTO_TIMEZONE,
// TODO(b/260573124): add correct enforcing admin when permission changes are
// merged.
- EnforcingAdmin.createEnterpriseEnforcingAdmin(caller.getComponentName()),
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(
+ caller.getComponentName(), caller.getUserId()),
enabled);
} else {
mInjector.binderWithCleanCallingIdentity(() ->
@@ -8107,8 +8137,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
+
+ if (isPermissionCheckFlagEnabled()) {
+ // The effect of this policy is device-wide.
+ enforceCanQuery(SET_TIME_ZONE, UserHandle.USER_ALL);
+ } else {
+ Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(
+ caller));
+ }
return mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) > 0;
}
@@ -12599,7 +12636,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
if (isCoexistenceEnabled(caller)) {
- EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(who);
+ EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
+ who, caller.getUserId());
if (packages.length == 0) {
mDevicePolicyEngine.removeLocalPolicy(
PolicyDefinition.LOCK_TASK,
@@ -12619,7 +12657,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
mDevicePolicyEngine.setLocalPolicy(
PolicyDefinition.LOCK_TASK,
- EnforcingAdmin.createEnterpriseEnforcingAdmin(who),
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(who, caller.getUserId()),
policy,
caller.getUserId());
}
@@ -12715,7 +12753,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_LOCK_TASK_FEATURES);
}
if (isCoexistenceEnabled(caller)) {
- EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(who);
+ EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(who, userHandle);
LockTaskPolicy currentPolicy = mDevicePolicyEngine.getLocalPolicy(
PolicyDefinition.LOCK_TASK,
caller.getUserId()).getPoliciesSetByAdmins().get(admin);
@@ -12728,7 +12766,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
mDevicePolicyEngine.setLocalPolicy(
PolicyDefinition.LOCK_TASK,
- EnforcingAdmin.createEnterpriseEnforcingAdmin(who),
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(who, userHandle),
policy,
caller.getUserId());
} else {
@@ -13031,8 +13069,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ if (isPermissionCheckFlagEnabled()) {
+ // This is a global action.
+ enforcePermission(SET_TIME, UserHandle.USER_ALL);
+ } else {
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ }
// Don't allow set time when auto time is on.
if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) == 1) {
@@ -13051,8 +13095,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ if (isPermissionCheckFlagEnabled()) {
+ // This is a global action.
+ enforcePermission(SET_TIME_ZONE, UserHandle.USER_ALL);
+ } else {
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ }
// Don't allow set timezone when auto timezone is on.
if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) == 1) {
@@ -13657,6 +13707,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
broadcastIntentToDevicePolicyManagerRoleHolder(intent, parentHandle);
}
+ @Override
+ public void enforcePermission(String permission, int targetUserId) {
+ DevicePolicyManagerService.this.enforcePermission(permission, targetUserId);
+ }
+
+ @Override
+ public boolean hasPermission(String permission, int targetUserId) {
+ return DevicePolicyManagerService.this.hasPermission(permission, targetUserId);
+ }
+
private void broadcastIntentToCrossProfileManifestReceivers(
Intent intent, UserHandle userHandle, boolean requiresPermission) {
final int userId = userHandle.getIdentifier();
@@ -14362,7 +14422,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
// TODO(b/260573124): Add correct enforcing admin when permission changes are
// merged, and don't forget to handle delegates! Enterprise admins assume
// component name isn't null.
- EnforcingAdmin.createEnterpriseEnforcingAdmin(caller.getComponentName()),
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(
+ caller.getComponentName(), caller.getUserId()),
grantState,
caller.getUserId());
// TODO: update javadoc to reflect that callback no longer return success/failure
@@ -18408,14 +18469,25 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
}
+
private String getDevicePolicyManagementRoleHolderPackageName(Context context) {
RoleManager roleManager = context.getSystemService(RoleManager.class);
- List<String> roleHolders =
- roleManager.getRoleHolders(RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT);
- if (roleHolders.isEmpty()) {
- return null;
- }
- return roleHolders.get(0);
+
+ // Calling identity needs to be cleared as this method is used in the permissions checks.
+ return mInjector.binderWithCleanCallingIdentity(() -> {
+ List<String> roleHolders =
+ roleManager.getRoleHolders(RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT);
+ if (roleHolders.isEmpty()) {
+ return null;
+ }
+ return roleHolders.get(0);
+ });
+ }
+
+ private boolean isDevicePolicyManagementRoleHolder(CallerIdentity caller) {
+ String devicePolicyManagementRoleHolderPackageName =
+ getDevicePolicyManagementRoleHolderPackageName(mContext);
+ return caller.getPackageName().equals(devicePolicyManagementRoleHolderPackageName);
}
private void resetInteractAcrossProfilesAppOps() {
@@ -19513,6 +19585,192 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
});
}
+ // DPC types
+ private static final int DEFAULT_DEVICE_OWNER = 0;
+ private static final int FINANCED_DEVICE_OWNER = 1;
+ private static final int PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE = 2;
+ private static final int PROFILE_OWNER_ON_USER_0 = 3;
+ private static final int PROFILE_OWNER = 4;
+
+ // Permissions of existing DPC types.
+ private static final List<String> DEFAULT_DEVICE_OWNER_PERMISSIONS = List.of(
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL,
+ SET_TIME,
+ SET_TIME_ZONE);
+ private static final List<String> FINANCED_DEVICE_OWNER_PERMISSIONS = List.of(
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL);
+ private static final List<String> PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE_PERMISSIONS =
+ List.of(
+ MANAGE_DEVICE_POLICY_ACROSS_USERS,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL,
+ SET_TIME,
+ SET_TIME_ZONE);
+ private static final List<String> PROFILE_OWNER_ON_USER_0_PERMISSIONS = List.of(
+ SET_TIME,
+ SET_TIME_ZONE);
+ private static final List<String> PROFILE_OWNER_PERMISSIONS = List.of(
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL);
+
+ private static final HashMap<Integer, List<String>> DPC_PERMISSIONS = new HashMap<>();
+ {
+ DPC_PERMISSIONS.put(DEFAULT_DEVICE_OWNER, DEFAULT_DEVICE_OWNER_PERMISSIONS);
+ DPC_PERMISSIONS.put(FINANCED_DEVICE_OWNER, FINANCED_DEVICE_OWNER_PERMISSIONS);
+ DPC_PERMISSIONS.put(PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE,
+ PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE_PERMISSIONS);
+ DPC_PERMISSIONS.put(PROFILE_OWNER_ON_USER_0, PROFILE_OWNER_ON_USER_0_PERMISSIONS);
+ DPC_PERMISSIONS.put(PROFILE_OWNER, PROFILE_OWNER_PERMISSIONS);
+ }
+
+ //TODO(b/254253251) Fill this map in as new permissions are added for policies.
+ private static final HashMap<String, Integer> ACTIVE_ADMIN_POLICIES = new HashMap<>();
+
+ private static final HashMap<String, String> CROSS_USER_PERMISSIONS =
+ new HashMap<>();
+ {
+ // Auto time is intrinsically global so there is no cross-user permission.
+ CROSS_USER_PERMISSIONS.put(SET_TIME, null);
+ CROSS_USER_PERMISSIONS.put(SET_TIME_ZONE, null);
+ }
+
+ /**
+ * Checks if the calling process has been granted permission to apply a device policy on a
+ * specific user.
+ * The given permission will be checked along with its associated cross-user permission if it
+ * exists and the target user is different to the calling user.
+ *
+ * @param permission The name of the permission being checked.
+ * @param targetUserId The userId of the user which the caller needs permission to act on.
+ * @throws SecurityException if the caller has not been granted the given permission,
+ * the associtated cross-user permission if the caller's user is different to the target user.
+ */
+ private void enforcePermission(String permission, int targetUserId)
+ throws SecurityException {
+ if (!hasPermission(permission, targetUserId)) {
+ throw new SecurityException("Caller does not have the required permissions for "
+ + "this user. Permissions required: {"
+ + permission
+ + ", "
+ + CROSS_USER_PERMISSIONS.get(permission)
+ + "}");
+ }
+ }
+
+ /**
+ * Return whether the calling process has been granted permission to query a device policy on
+ * a specific user.
+ *
+ * @param permission The name of the permission being checked.
+ * @param targetUserId The userId of the user which the caller needs permission to act on.
+ * @throws SecurityException if the caller has not been granted the given permission,
+ * the associatated cross-user permission if the caller's user is different to the target user
+ * and if the user has not been granted {@link QUERY_ADMIN_POLICY}.
+ */
+ private void enforceCanQuery(String permission, int targetUserId) throws SecurityException {
+ if (hasPermission(QUERY_ADMIN_POLICY)) {
+ return;
+ }
+ enforcePermission(permission, targetUserId);
+ }
+
+ /**
+ * Return whether the calling process has been granted permission to apply a device policy on
+ * a specific user.
+ *
+ * @param permission The name of the permission being checked.
+ * @param targetUserId The userId of the user which the caller needs permission to act on.
+ */
+ private boolean hasPermission(String permission, int targetUserId) {
+ boolean hasPermissionOnOwnUser = hasPermission(permission);
+ boolean hasPermissionOnTargetUser = true;
+ if (hasPermissionOnOwnUser & getCallerIdentity().getUserId() != targetUserId) {
+ hasPermissionOnTargetUser = hasPermission(CROSS_USER_PERMISSIONS.get(permission));
+ }
+ return hasPermissionOnOwnUser && hasPermissionOnTargetUser;
+ }
+
+ /**
+ * Return whether the calling process has been granted the given permission.
+ *
+ * @param permission The name of the permission being checked.
+ */
+ private boolean hasPermission(String permission) {
+ if (permission == null) {
+ return true;
+ }
+
+ CallerIdentity caller = getCallerIdentity();
+
+ // Check if the caller holds the permission
+ if (mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) {
+ return true;
+ }
+ // Check the permissions of DPCs
+ if (isDefaultDeviceOwner(caller)) {
+ return DPC_PERMISSIONS.get(DEFAULT_DEVICE_OWNER).contains(permission);
+ }
+ if (isFinancedDeviceOwner(caller)) {
+ return DPC_PERMISSIONS.get(FINANCED_DEVICE_OWNER).contains(permission);
+ }
+ if (isProfileOwnerOfOrganizationOwnedDevice(caller)) {
+ return DPC_PERMISSIONS.get(PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE).contains(
+ permission);
+ }
+ if (isProfileOwnerOnUser0(caller)) {
+ return DPC_PERMISSIONS.get(PROFILE_OWNER_ON_USER_0).contains(permission);
+ }
+ if (isProfileOwner(caller)) {
+ return DPC_PERMISSIONS.get(PROFILE_OWNER).contains(permission);
+ }
+ // Check the permission for the role-holder
+ if (isDevicePolicyManagementRoleHolder(caller)) {
+ return anyDpcHasPermission(permission, mContext.getUserId());
+ }
+ // Check if the caller is an active admin that uses a certain policy.
+ if (ACTIVE_ADMIN_POLICIES.containsKey(permission)) {
+ return getActiveAdminForCallerLocked(
+ null, ACTIVE_ADMIN_POLICIES.get(permission), false) != null;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns whether there is a DPC on the given user that has been granted the given permission.
+ *
+ * @param permission The name of the permission being checked.
+ * @param userId The id of the user to check.
+ */
+ private boolean anyDpcHasPermission(String permission, int userId) {
+ if (mOwners.isDefaultDeviceOwnerUserId(userId)) {
+ return DPC_PERMISSIONS.get(DEFAULT_DEVICE_OWNER).contains(permission);
+ }
+ if (mOwners.isFinancedDeviceOwnerUserId(userId)) {
+ return DPC_PERMISSIONS.get(FINANCED_DEVICE_OWNER).contains(permission);
+ }
+ if (mOwners.isProfileOwnerOfOrganizationOwnedDevice(userId)) {
+ return DPC_PERMISSIONS.get(PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE).contains(
+ permission);
+ }
+ if (userId == 0 && mOwners.hasProfileOwner(0)) {
+ return DPC_PERMISSIONS.get(PROFILE_OWNER_ON_USER_0).contains(permission);
+ }
+ if (mOwners.hasProfileOwner(userId)) {
+ return DPC_PERMISSIONS.get(PROFILE_OWNER).contains(permission);
+ }
+ return false;
+ }
+
+ private boolean isPermissionCheckFlagEnabled() {
+ return DeviceConfig.getBoolean(
+ NAMESPACE_DEVICE_POLICY_MANAGER,
+ PERMISSION_BASED_ACCESS_EXPERIMENT_FLAG,
+ DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG);
+ }
+
// TODO(b/260560985): properly gate coexistence changes
private boolean isCoexistenceEnabled(CallerIdentity caller) {
return isCoexistenceFlagEnabled()
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
index 9261d59ce1e3..00e48eb67ab0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
@@ -69,16 +69,18 @@ final class EnforcingAdmin {
return new EnforcingAdmin(packageName, userId);
}
- static EnforcingAdmin createEnterpriseEnforcingAdmin(@NonNull ComponentName componentName) {
+ static EnforcingAdmin createEnterpriseEnforcingAdmin(
+ @NonNull ComponentName componentName, int userId) {
Objects.requireNonNull(componentName);
return new EnforcingAdmin(
- componentName.getPackageName(), componentName, Set.of(DPC_AUTHORITY));
+ componentName.getPackageName(), componentName, Set.of(DPC_AUTHORITY), userId);
}
- static EnforcingAdmin createDeviceAdminEnforcingAdmin(ComponentName componentName) {
+ static EnforcingAdmin createDeviceAdminEnforcingAdmin(ComponentName componentName, int userId) {
Objects.requireNonNull(componentName);
return new EnforcingAdmin(
- componentName.getPackageName(), componentName, Set.of(DEVICE_ADMIN_AUTHORITY));
+ componentName.getPackageName(), componentName, Set.of(DEVICE_ADMIN_AUTHORITY),
+ userId);
}
static String getRoleAuthorityOf(String roleName) {
@@ -86,7 +88,7 @@ final class EnforcingAdmin {
}
private EnforcingAdmin(
- String packageName, ComponentName componentName, Set<String> authorities) {
+ String packageName, ComponentName componentName, Set<String> authorities, int userId) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(componentName);
Objects.requireNonNull(authorities);
@@ -96,7 +98,7 @@ final class EnforcingAdmin {
mPackageName = packageName;
mComponentName = componentName;
mAuthorities = new HashSet<>(authorities);
- mUserId = -1; // not needed for non role authorities
+ mUserId = userId;
}
private EnforcingAdmin(String packageName, int userId) {
@@ -145,6 +147,15 @@ final class EnforcingAdmin {
return getAuthorities().contains(authority);
}
+ @NonNull
+ String getPackageName() {
+ return mPackageName;
+ }
+
+ int getUserId() {
+ return mUserId;
+ }
+
/**
* For two EnforcingAdmins to be equal they must:
*
@@ -188,11 +199,11 @@ final class EnforcingAdmin {
void saveToXml(TypedXmlSerializer serializer) throws IOException {
serializer.attribute(/* namespace= */ null, ATTR_PACKAGE_NAME, mPackageName);
serializer.attributeBoolean(/* namespace= */ null, ATTR_IS_ROLE, mIsRoleAuthority);
- if (mIsRoleAuthority) {
- serializer.attributeInt(/* namespace= */ null, ATTR_USER_ID, mUserId);
- } else {
+ serializer.attributeInt(/* namespace= */ null, ATTR_USER_ID, mUserId);
+ if (!mIsRoleAuthority) {
serializer.attribute(
/* namespace= */ null, ATTR_CLASS_NAME, mComponentName.getClassName());
+ // Role authorities get recomputed on load so no need to save them.
serializer.attribute(
/* namespace= */ null,
ATTR_AUTHORITIES,
@@ -205,15 +216,15 @@ final class EnforcingAdmin {
String packageName = parser.getAttributeValue(/* namespace= */ null, ATTR_PACKAGE_NAME);
boolean isRoleAuthority = parser.getAttributeBoolean(/* namespace= */ null, ATTR_IS_ROLE);
String authoritiesStr = parser.getAttributeValue(/* namespace= */ null, ATTR_AUTHORITIES);
+ int userId = parser.getAttributeInt(/* namespace= */ null, ATTR_USER_ID);
if (isRoleAuthority) {
- int userId = parser.getAttributeInt(/* namespace= */ null, ATTR_USER_ID);
return new EnforcingAdmin(packageName, userId);
} else {
String className = parser.getAttributeValue(/* namespace= */ null, ATTR_CLASS_NAME);
ComponentName componentName = new ComponentName(packageName, className);
Set<String> authorities = Set.of(authoritiesStr.split(ATTR_AUTHORITIES_SEPARATOR));
- return new EnforcingAdmin(packageName, componentName, authorities);
+ return new EnforcingAdmin(packageName, componentName, authorities, userId);
}
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index 6f172e4515fc..581a19913530 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -19,6 +19,7 @@ package com.android.server.devicepolicy;
import static android.app.admin.DevicePolicyManager.DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_DEFAULT;
import static android.app.admin.DevicePolicyManager.DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_FLAG;
import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT;
+import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
import static com.android.server.devicepolicy.DeviceStateCacheImpl.NO_DEVICE_OWNER;
@@ -455,6 +456,23 @@ class Owners {
}
}
+ boolean isDefaultDeviceOwnerUserId(int userId) {
+ synchronized (mData) {
+ return mData.mDeviceOwner != null
+ && mData.mDeviceOwnerUserId == userId
+ && getDeviceOwnerType(getDeviceOwnerPackageName()) == DEVICE_OWNER_TYPE_DEFAULT;
+ }
+ }
+
+ boolean isFinancedDeviceOwnerUserId(int userId) {
+ synchronized (mData) {
+ return mData.mDeviceOwner != null
+ && mData.mDeviceOwnerUserId == userId
+ && getDeviceOwnerType(getDeviceOwnerPackageName())
+ == DEVICE_OWNER_TYPE_FINANCED;
+ }
+ }
+
boolean hasProfileOwner(int userId) {
synchronized (mData) {
return getProfileOwnerComponent(userId) != null;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index a787a0b3943b..c684af39a25f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -19,12 +19,16 @@ package com.android.server.devicepolicy;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.admin.DevicePolicyManager;
+import android.app.admin.PolicyUpdatesReceiver;
import android.content.Context;
+import android.os.Bundle;
import com.android.internal.util.function.QuadFunction;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.List;
@@ -44,8 +48,9 @@ final class PolicyDefinition<V> {
private static final String ATTR_POLICY_KEY = "policy-key";
private static final String ATTR_POLICY_DEFINITION_KEY = "policy-type-key";
- private static final String ATTR_CALLBACK_ARGS = "callback-args";
- private static final String ATTR_CALLBACK_ARGS_SEPARATOR = ";";
+ private static final String ATTR_CALLBACK_ARGS_SIZE = "size";
+ private static final String ATTR_CALLBACK_ARGS_KEY = "key";
+ private static final String ATTR_CALLBACK_ARGS_VALUE = "value";
static PolicyDefinition<Boolean> AUTO_TIMEZONE = new PolicyDefinition<>(
@@ -53,7 +58,7 @@ final class PolicyDefinition<V> {
// auto timezone is enabled by default, hence disabling it is more restrictive.
FALSE_MORE_RESTRICTIVE,
POLICY_FLAG_GLOBAL_ONLY_POLICY,
- (Boolean value, Context context, Integer userId, String[] args) ->
+ (Boolean value, Context context, Integer userId, Bundle args) ->
PolicyEnforcerCallbacks.setAutoTimezoneEnabled(value, context),
new BooleanPolicySerializer());
@@ -75,9 +80,11 @@ final class PolicyDefinition<V> {
static PolicyDefinition<Integer> PERMISSION_GRANT(
@NonNull String packageName, @NonNull String permission) {
+ Bundle callbackArgs = new Bundle();
+ callbackArgs.putString(PolicyUpdatesReceiver.EXTRA_PACKAGE_NAME, packageName);
+ callbackArgs.putString(PolicyUpdatesReceiver.EXTRA_PERMISSION_NAME, permission);
return PERMISSION_GRANT_NO_ARGS.setArgs(
- DevicePolicyManager.PERMISSION_GRANT_POLICY(packageName, permission),
- new String[]{packageName, permission});
+ DevicePolicyManager.PERMISSION_GRANT_POLICY(packageName, permission), callbackArgs);
}
static PolicyDefinition<LockTaskPolicy> LOCK_TASK = new PolicyDefinition<>(
@@ -87,13 +94,14 @@ final class PolicyDefinition<V> {
EnforcingAdmin.getRoleAuthorityOf("DeviceLock"),
EnforcingAdmin.DPC_AUTHORITY)),
POLICY_FLAG_LOCAL_ONLY_POLICY,
- (LockTaskPolicy value, Context context, Integer userId, String[] args) ->
+ (LockTaskPolicy value, Context context, Integer userId, Bundle args) ->
PolicyEnforcerCallbacks.setLockTask(value, context, userId),
new LockTaskPolicy.LockTaskPolicySerializer());
private static Map<String, PolicyDefinition<?>> sPolicyDefinitions = Map.of(
DevicePolicyManager.AUTO_TIMEZONE_POLICY, AUTO_TIMEZONE,
- DevicePolicyManager.PERMISSION_GRANT_POLICY_KEY, PERMISSION_GRANT_NO_ARGS
+ DevicePolicyManager.PERMISSION_GRANT_POLICY_KEY, PERMISSION_GRANT_NO_ARGS,
+ DevicePolicyManager.LOCK_TASK_POLICY, LOCK_TASK
);
@@ -103,19 +111,30 @@ final class PolicyDefinition<V> {
private final int mPolicyFlags;
// A function that accepts policy to apple, context, userId, callback arguments, and returns
// true if the policy has been enforced successfully.
- private final QuadFunction<V, Context, Integer, String[], Boolean> mPolicyEnforcerCallback;
- private final String[] mCallbackArgs;
+ private final QuadFunction<V, Context, Integer, Bundle, Boolean> mPolicyEnforcerCallback;
+ private final Bundle mCallbackArgs;
private final PolicySerializer<V> mPolicySerializer;
- private PolicyDefinition<V> setArgs(String key, String[] callbackArgs) {
+ private PolicyDefinition<V> setArgs(String key, Bundle callbackArgs) {
return new PolicyDefinition<>(key, mPolicyDefinitionKey, mResolutionMechanism,
mPolicyFlags, mPolicyEnforcerCallback, mPolicySerializer, callbackArgs);
}
+ @NonNull
String getPolicyKey() {
return mPolicyKey;
}
+ @NonNull
+ String getPolicyDefinitionKey() {
+ return mPolicyDefinitionKey;
+ }
+
+ @Nullable
+ Bundle getCallbackArgs() {
+ return mCallbackArgs;
+ }
+
/**
* Returns {@code true} if the policy is a global policy by nature and can't be applied locally.
*/
@@ -146,7 +165,7 @@ final class PolicyDefinition<V> {
private PolicyDefinition(
String key,
ResolutionMechanism<V> resolutionMechanism,
- QuadFunction<V, Context, Integer, String[], Boolean> policyEnforcerCallback,
+ QuadFunction<V, Context, Integer, Bundle, Boolean> policyEnforcerCallback,
PolicySerializer<V> policySerializer) {
this(key, resolutionMechanism, POLICY_FLAG_NONE, policyEnforcerCallback, policySerializer);
}
@@ -159,7 +178,7 @@ final class PolicyDefinition<V> {
String key,
ResolutionMechanism<V> resolutionMechanism,
int policyFlags,
- QuadFunction<V, Context, Integer, String[], Boolean> policyEnforcerCallback,
+ QuadFunction<V, Context, Integer, Bundle, Boolean> policyEnforcerCallback,
PolicySerializer<V> policySerializer) {
this(key, key, resolutionMechanism, policyFlags, policyEnforcerCallback,
policySerializer, /* callbackArs= */ null);
@@ -174,9 +193,9 @@ final class PolicyDefinition<V> {
String policyDefinitionKey,
ResolutionMechanism<V> resolutionMechanism,
int policyFlags,
- QuadFunction<V, Context, Integer, String[], Boolean> policyEnforcerCallback,
+ QuadFunction<V, Context, Integer, Bundle, Boolean> policyEnforcerCallback,
PolicySerializer<V> policySerializer,
- String[] callbackArgs) {
+ Bundle callbackArgs) {
mPolicyKey = policyKey;
mPolicyDefinitionKey = policyDefinitionKey;
mResolutionMechanism = resolutionMechanism;
@@ -193,24 +212,39 @@ final class PolicyDefinition<V> {
serializer.attribute(/* namespace= */ null, ATTR_POLICY_KEY, mPolicyKey);
serializer.attribute(
/* namespace= */ null, ATTR_POLICY_DEFINITION_KEY, mPolicyDefinitionKey);
+ serializer.attributeInt(
+ /* namespace= */ null, ATTR_CALLBACK_ARGS_SIZE,
+ mCallbackArgs == null ? 0 : mCallbackArgs.size());
if (mCallbackArgs != null) {
- serializer.attribute(/* namespace= */ null, ATTR_CALLBACK_ARGS,
- String.join(ATTR_CALLBACK_ARGS_SEPARATOR, mCallbackArgs));
+ int i = 0;
+ for (String key : mCallbackArgs.keySet()) {
+ serializer.attribute(/* namespace= */ null,
+ ATTR_CALLBACK_ARGS_KEY + i, key);
+ serializer.attribute(/* namespace= */ null,
+ ATTR_CALLBACK_ARGS_VALUE + i, mCallbackArgs.getString(key));
+ i++;
+ }
}
}
- static <V> PolicyDefinition<V> readFromXml(TypedXmlPullParser parser) {
+ static <V> PolicyDefinition<V> readFromXml(TypedXmlPullParser parser)
+ throws XmlPullParserException, IOException {
String policyKey = parser.getAttributeValue(/* namespace= */ null, ATTR_POLICY_KEY);
String policyDefinitionKey = parser.getAttributeValue(
/* namespace= */ null, ATTR_POLICY_DEFINITION_KEY);
- String callbackArgsStr = parser.getAttributeValue(
- /* namespace= */ null, ATTR_CALLBACK_ARGS);
- String[] callbackArgs = callbackArgsStr == null
- ? null
- : callbackArgsStr.split(ATTR_CALLBACK_ARGS_SEPARATOR);
+ int size = parser.getAttributeInt(/* namespace= */ null, ATTR_CALLBACK_ARGS_SIZE);
+ Bundle callbackArgs = new Bundle();
+
+ for (int i = 0; i < size; i++) {
+ String key = parser.getAttributeValue(
+ /* namespace= */ null, ATTR_CALLBACK_ARGS_KEY + i);
+ String value = parser.getAttributeValue(
+ /* namespace= */ null, ATTR_CALLBACK_ARGS_VALUE + i);
+ callbackArgs.putString(key, value);
+ }
// TODO: can we avoid casting?
- if (callbackArgs == null) {
+ if (callbackArgs.isEmpty()) {
return (PolicyDefinition<V>) sPolicyDefinitions.get(policyDefinitionKey);
} else {
return (PolicyDefinition<V>) sPolicyDefinitions.get(policyDefinitionKey).setArgs(
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index 74b6f9ea114f..c745b31afd9c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -19,9 +19,11 @@ package com.android.server.devicepolicy;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.admin.DevicePolicyManager;
+import android.app.admin.PolicyUpdatesReceiver;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
+import android.os.Bundle;
import android.os.UserHandle;
import android.permission.AdminPermissionControlParams;
import android.permission.PermissionControllerManager;
@@ -53,14 +55,17 @@ final class PolicyEnforcerCallbacks {
static boolean setPermissionGrantState(
@Nullable Integer grantState, @NonNull Context context, int userId,
- @NonNull String[] args) {
+ @NonNull Bundle args) {
return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
- if (args == null || args.length < 2) {
+ if (args == null
+ || !args.containsKey(PolicyUpdatesReceiver.EXTRA_PACKAGE_NAME)
+ || !args.containsKey(PolicyUpdatesReceiver.EXTRA_PERMISSION_NAME)) {
throw new IllegalArgumentException("Package name and permission name must be "
- + "provided as arguments");
+ + "provided as arguments.");
}
- String packageName = args[0];
- String permissionName = args[1];
+
+ String packageName = args.getString(PolicyUpdatesReceiver.EXTRA_PACKAGE_NAME);
+ String permissionName = args.getString(PolicyUpdatesReceiver.EXTRA_PERMISSION_NAME);
Objects.requireNonNull(packageName);
Objects.requireNonNull(permissionName);
Objects.requireNonNull(context);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
index d3dee98cf7ba..ffde5f858ce6 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
@@ -119,10 +119,13 @@ final class PolicyState<V> {
static <V> PolicyState<V> readFromXml(TypedXmlPullParser parser)
throws IOException, XmlPullParserException {
+
PolicyDefinition<V> policyDefinition = PolicyDefinition.readFromXml(parser);
- LinkedHashMap<EnforcingAdmin, V> adminsPolicy = new LinkedHashMap<>();
+
V currentResolvedPolicy = policyDefinition.readPolicyValueFromXml(
parser, ATTR_RESOLVED_POLICY);
+
+ LinkedHashMap<EnforcingAdmin, V> policiesSetByAdmins = new LinkedHashMap<>();
int outerDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
String tag = parser.getName();
@@ -134,12 +137,12 @@ final class PolicyState<V> {
if (XmlUtils.nextElementWithin(parser, adminPolicyDepth)
&& parser.getName().equals(TAG_ENFORCING_ADMIN_ENTRY)) {
admin = EnforcingAdmin.readFromXml(parser);
- adminsPolicy.put(admin, value);
+ policiesSetByAdmins.put(admin, value);
}
} else {
Log.e(DevicePolicyEngine.TAG, "Unknown tag: " + tag);
}
}
- return new PolicyState<V>(policyDefinition, adminsPolicy, currentResolvedPolicy);
+ return new PolicyState<V>(policyDefinition, policiesSetByAdmins, currentResolvedPolicy);
}
}
diff --git a/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java b/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java
index e0812d6a77ea..73ddbe8cec7c 100644
--- a/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java
@@ -78,6 +78,7 @@ import org.robolectric.shadows.ShadowLooper;
import org.robolectric.shadows.ShadowPackageManager;
import java.util.ArrayDeque;
+import java.util.Arrays;
@RunWith(RobolectricTestRunner.class)
@Config(
@@ -196,7 +197,7 @@ public class ActiveRestoreSessionTest {
mShadowApplication.grantPermissions(android.Manifest.permission.BACKUP);
TransportMock transportMock = setUpTransport(mTransport);
when(transportMock.transport.getAvailableRestoreSets())
- .thenReturn(new RestoreSet[] {mRestoreSet1, mRestoreSet2});
+ .thenReturn(Arrays.asList(mRestoreSet1, mRestoreSet2));
IRestoreSession restoreSession = createActiveRestoreSession(PACKAGE_1, mTransport);
int result = restoreSession.getAvailableRestoreSets(mObserver, mMonitor);
@@ -214,7 +215,8 @@ public class ActiveRestoreSessionTest {
public void testGetAvailableRestoreSets_forEmptyRestoreSets() throws Exception {
mShadowApplication.grantPermissions(android.Manifest.permission.BACKUP);
TransportMock transportMock = setUpTransport(mTransport);
- when(transportMock.transport.getAvailableRestoreSets()).thenReturn(new RestoreSet[0]);
+ when(transportMock.transport.getAvailableRestoreSets()).thenReturn(
+ Arrays.asList(new RestoreSet[0]));
IRestoreSession restoreSession = createActiveRestoreSession(PACKAGE_1, mTransport);
int result = restoreSession.getAvailableRestoreSets(mObserver, mMonitor);
@@ -593,7 +595,7 @@ public class ActiveRestoreSessionTest {
new ActiveRestoreSession(
mBackupManagerService, packageName, transport.transportName,
mBackupEligibilityRules);
- restoreSession.setRestoreSets(restoreSets);
+ restoreSession.setRestoreSets(Arrays.asList(restoreSets));
return restoreSession;
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index 1619856e005a..f0e3f3f508a5 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -89,6 +89,10 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag
"sortReceivers",
"sortServices",
"setAllComponentsDirectBootAware",
+ "getUsesLibrariesSorted",
+ "getUsesOptionalLibrariesSorted",
+ "getUsesSdkLibrariesSorted",
+ "getUsesStaticLibrariesSorted",
// Tested through setting minor/major manually
"setLongVersionCode",
"getLongVersionCode",
diff --git a/services/tests/mockingservicestests/src/com/android/server/companion/virtual/CameraAccessControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/companion/virtual/CameraAccessControllerTest.java
index 145e66c92f14..757d27b6b569 100644
--- a/services/tests/mockingservicestests/src/com/android/server/companion/virtual/CameraAccessControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/companion/virtual/CameraAccessControllerTest.java
@@ -18,6 +18,8 @@ package com.android.server.companion.virtual;
import static android.hardware.camera2.CameraInjectionSession.InjectionStatusCallback.ERROR_INJECTION_UNSUPPORTED;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
@@ -116,6 +118,11 @@ public class CameraAccessControllerTest {
}
@Test
+ public void getUserId_returnsCorrectId() {
+ assertThat(mController.getUserId()).isEqualTo(mContext.getUserId());
+ }
+
+ @Test
public void onCameraOpened_uidNotRunning_noCameraBlocking() throws CameraAccessException {
when(mDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(
eq(mTestAppInfo.uid))).thenReturn(false);
diff --git a/services/tests/mockingservicestests/src/com/android/server/companion/virtual/OWNERS b/services/tests/mockingservicestests/src/com/android/server/companion/virtual/OWNERS
new file mode 100644
index 000000000000..2e475a9a2742
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/companion/virtual/OWNERS
@@ -0,0 +1 @@
+include /services/companion/java/com/android/server/companion/virtual/OWNERS \ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 89eaa2c3d85a..759b0497044f 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -203,6 +203,7 @@ public class VirtualDeviceManagerServiceTest {
private VirtualDeviceImpl mDeviceImpl;
private InputController mInputController;
private SensorController mSensorController;
+ private CameraAccessController mCameraAccessController;
private AssociationInfo mAssociationInfo;
private VirtualDeviceManagerService mVdms;
private VirtualDeviceManagerInternal mLocalService;
@@ -237,6 +238,8 @@ public class VirtualDeviceManagerServiceTest {
@Mock
private IAudioConfigChangedCallback mConfigChangedCallback;
@Mock
+ private CameraAccessController.CameraAccessBlockedCallback mCameraAccessBlockedCallback;
+ @Mock
private ApplicationInfo mApplicationInfoMock;
@Mock
IInputManager mIInputManagerMock;
@@ -325,6 +328,8 @@ public class VirtualDeviceManagerServiceTest {
new Handler(TestableLooper.get(this).getLooper()),
mContext.getSystemService(WindowManager.class), threadVerifier);
mSensorController = new SensorController(new Object(), VIRTUAL_DEVICE_ID_1);
+ mCameraAccessController =
+ new CameraAccessController(mContext, mLocalService, mCameraAccessBlockedCallback);
mAssociationInfo = new AssociationInfo(/* associationId= */ 1, 0, null,
MacAddress.BROADCAST_ADDRESS, "", null, null, true, false, false, 0, 0);
@@ -394,12 +399,7 @@ public class VirtualDeviceManagerServiceTest {
.setBlockedActivities(getBlockedActivities())
.setDevicePolicy(POLICY_TYPE_SENSORS, DEVICE_POLICY_CUSTOM)
.build();
- mDeviceImpl = new VirtualDeviceImpl(mContext,
- mAssociationInfo, new Binder(), /* ownerUid */ 0, VIRTUAL_DEVICE_ID_1,
- mInputController, mSensorController,
- /* onDeviceCloseListener= */ (int deviceId) -> {},
- mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params);
- mVdms.addVirtualDevice(mDeviceImpl);
+ mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID_1, DEVICE_OWNER_UID_1, params);
assertThat(mVdm.getDevicePolicy(mDeviceImpl.getDeviceId(), POLICY_TYPE_SENSORS))
.isEqualTo(DEVICE_POLICY_CUSTOM);
@@ -558,6 +558,21 @@ public class VirtualDeviceManagerServiceTest {
}
@Test
+ public void cameraAccessController_observerCountUpdated() {
+ assertThat(mCameraAccessController.getObserverCount()).isEqualTo(1);
+
+ VirtualDeviceImpl secondDevice =
+ createVirtualDevice(VIRTUAL_DEVICE_ID_2, DEVICE_OWNER_UID_2);
+ assertThat(mCameraAccessController.getObserverCount()).isEqualTo(2);
+
+ mDeviceImpl.close();
+ assertThat(mCameraAccessController.getObserverCount()).isEqualTo(1);
+
+ secondDevice.close();
+ assertThat(mCameraAccessController.getObserverCount()).isEqualTo(0);
+ }
+
+ @Test
public void onVirtualDisplayRemovedLocked_doesNotThrowException() {
mDeviceImpl.onVirtualDisplayCreatedLocked(
mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
@@ -1543,14 +1558,18 @@ public class VirtualDeviceManagerServiceTest {
}
private VirtualDeviceImpl createVirtualDevice(int virtualDeviceId, int ownerUid) {
- VirtualDeviceParams params = new VirtualDeviceParams
- .Builder()
+ VirtualDeviceParams params = new VirtualDeviceParams.Builder()
.setBlockedActivities(getBlockedActivities())
.build();
+ return createVirtualDevice(virtualDeviceId, ownerUid, params);
+ }
+
+ private VirtualDeviceImpl createVirtualDevice(int virtualDeviceId, int ownerUid,
+ VirtualDeviceParams params) {
VirtualDeviceImpl virtualDeviceImpl = new VirtualDeviceImpl(mContext,
mAssociationInfo, new Binder(), ownerUid, virtualDeviceId,
- mInputController, mSensorController,
- /* onDeviceCloseListener= */ (int deviceId) -> {},
+ mInputController, mSensorController, mCameraAccessController,
+ /* onDeviceCloseListener= */ deviceId -> mVdms.removeVirtualDevice(deviceId),
mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params);
mVdms.addVirtualDevice(virtualDeviceImpl);
return virtualDeviceImpl;
diff --git a/services/tests/servicestests/src/com/android/server/display/ScreenOffBrightnessSensorControllerTest.java b/services/tests/servicestests/src/com/android/server/display/ScreenOffBrightnessSensorControllerTest.java
index ea04a193e569..5b10dc4e0bab 100644
--- a/services/tests/servicestests/src/com/android/server/display/ScreenOffBrightnessSensorControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/ScreenOffBrightnessSensorControllerTest.java
@@ -35,6 +35,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.server.testutils.OffsettableClock;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -73,6 +74,15 @@ public class ScreenOffBrightnessSensorControllerTest {
);
}
+ @After
+ public void tearDown() {
+ if (mController != null) {
+ // Stop the update Brightness loop.
+ mController.stop();
+ mController = null;
+ }
+ }
+
@Test
public void testBrightness() throws Exception {
when(mSensorManager.registerListener(any(SensorEventListener.class), eq(mLightSensor),
diff --git a/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt
index 44bdf5e49653..b5dad945ffac 100644
--- a/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt
@@ -20,6 +20,8 @@ import android.content.Context
import android.content.ContextWrapper
import android.graphics.Color
import android.hardware.input.IInputManager
+import android.hardware.input.IKeyboardBacklightListener
+import android.hardware.input.IKeyboardBacklightState
import android.hardware.input.InputManager
import android.hardware.lights.Light
import android.os.test.TestLooper
@@ -27,10 +29,6 @@ import android.platform.test.annotations.Presubmit
import android.view.InputDevice
import androidx.test.core.app.ApplicationProvider
import com.android.server.input.KeyboardBacklightController.BRIGHTNESS_LEVELS
-import java.io.FileNotFoundException
-import java.io.FileOutputStream
-import java.io.IOException
-import java.io.InputStream
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
@@ -45,6 +43,10 @@ import org.mockito.Mockito.eq
import org.mockito.Mockito.spy
import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnit
+import java.io.FileNotFoundException
+import java.io.FileOutputStream
+import java.io.IOException
+import java.io.InputStream
private fun createKeyboard(deviceId: Int): InputDevice =
InputDevice.Builder()
@@ -90,6 +92,7 @@ class KeyboardBacklightControllerTests {
private lateinit var dataStore: PersistentDataStore
private lateinit var testLooper: TestLooper
private var lightColorMap: HashMap<Int, Int> = HashMap()
+ private var lastBacklightState: KeyboardBacklightState? = null
@Before
fun setup() {
@@ -310,4 +313,75 @@ class KeyboardBacklightControllerTests {
lightColorMap[LIGHT_ID]
)
}
+
+ @Test
+ fun testKeyboardBacklightT_registerUnregisterListener() {
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+ `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+ // Initially backlight is at min
+ lightColorMap[LIGHT_ID] = Color.argb(BRIGHTNESS_LEVELS.first(), 0, 0, 0)
+
+ // Register backlight listener
+ val listener = KeyboardBacklightListener()
+ keyboardBacklightController.registerKeyboardBacklightListener(listener, 0)
+
+ lastBacklightState = null
+ keyboardBacklightController.incrementKeyboardBacklight(DEVICE_ID)
+ testLooper.dispatchNext()
+
+ assertEquals(
+ "Backlight state device Id should be $DEVICE_ID",
+ DEVICE_ID,
+ lastBacklightState!!.deviceId
+ )
+ assertEquals(
+ "Backlight state brightnessLevel should be " + 1,
+ 1,
+ lastBacklightState!!.brightnessLevel
+ )
+ assertEquals(
+ "Backlight state maxBrightnessLevel should be " + (BRIGHTNESS_LEVELS.size - 1),
+ (BRIGHTNESS_LEVELS.size - 1),
+ lastBacklightState!!.maxBrightnessLevel
+ )
+ assertEquals(
+ "Backlight state isTriggeredByKeyPress should be true",
+ true,
+ lastBacklightState!!.isTriggeredByKeyPress
+ )
+
+ // Unregister listener
+ keyboardBacklightController.unregisterKeyboardBacklightListener(listener, 0)
+
+ lastBacklightState = null
+ keyboardBacklightController.incrementKeyboardBacklight(DEVICE_ID)
+ testLooper.dispatchNext()
+
+ assertNull("Listener should not receive any updates", lastBacklightState)
+ }
+
+ inner class KeyboardBacklightListener : IKeyboardBacklightListener.Stub() {
+ override fun onBrightnessChanged(
+ deviceId: Int,
+ state: IKeyboardBacklightState,
+ isTriggeredByKeyPress: Boolean
+ ) {
+ lastBacklightState = KeyboardBacklightState(
+ deviceId,
+ state.brightnessLevel,
+ state.maxBrightnessLevel,
+ isTriggeredByKeyPress
+ )
+ }
+ }
+
+ class KeyboardBacklightState(
+ val deviceId: Int,
+ val brightnessLevel: Int,
+ val maxBrightnessLevel: Int,
+ val isTriggeredByKeyPress: Boolean
+ )
}
diff --git a/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml
index 1bc47759d078..b1d0f3dee8c0 100644
--- a/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml
+++ b/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml
@@ -18,13 +18,17 @@
package="com.android.servicestests.apps.simpleservicetestapp">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"/>
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<application>
<service android:name=".SimpleService"
android:exported="true" />
<service android:name=".SimpleFgService"
- android:exported="true" />
+ android:foregroundServiceType="specialUse"
+ android:exported="true">
+ <property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE" android:value="test" />
+ </service>
<service android:name=".SimpleIsolatedService"
android:isolatedProcess="true"
android:exported="true" />
diff --git a/services/tests/wmtests/Android.bp b/services/tests/wmtests/Android.bp
index 079d765868fd..2ce7cea08a3d 100644
--- a/services/tests/wmtests/Android.bp
+++ b/services/tests/wmtests/Android.bp
@@ -68,6 +68,10 @@ android_test {
"android.test.runner",
],
+ defaults: [
+ "modules-utils-testable-device-config-defaults",
+ ],
+
// These are not normally accessible from apps so they must be explicitly included.
jni_libs: [
"libdexmakerjvmtiagent",
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java
new file mode 100644
index 000000000000..d29b18f89f77
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+
+import android.platform.test.annotations.Presubmit;
+import android.view.Surface;
+import android.view.WindowInsets.Type;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test class for {@link DisplayRotationImmersiveAppCompatPolicy}.
+ *
+ * Build/Install/Run:
+ * atest WmTests:DisplayRotationImmersiveAppCompatPolicyTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class DisplayRotationImmersiveAppCompatPolicyTests extends WindowTestsBase {
+
+ private DisplayRotationImmersiveAppCompatPolicy mPolicy;
+
+ private LetterboxConfiguration mMockLetterboxConfiguration;
+ private ActivityRecord mMockActivityRecord;
+ private Task mMockTask;
+ private WindowState mMockWindowState;
+
+ @Before
+ public void setUp() throws Exception {
+ mMockActivityRecord = mock(ActivityRecord.class);
+ mMockTask = mock(Task.class);
+ when(mMockTask.getWindowingMode()).thenReturn(WINDOWING_MODE_FULLSCREEN);
+ when(mMockActivityRecord.getTask()).thenReturn(mMockTask);
+ when(mMockActivityRecord.areBoundsLetterboxed()).thenReturn(false);
+ when(mMockActivityRecord.getRequestedConfigurationOrientation()).thenReturn(
+ ORIENTATION_LANDSCAPE);
+ mMockWindowState = mock(WindowState.class);
+ when(mMockWindowState.getRequestedVisibleTypes()).thenReturn(0);
+ when(mMockActivityRecord.findMainWindow()).thenReturn(mMockWindowState);
+
+ spy(mDisplayContent);
+ doReturn(mMockActivityRecord).when(mDisplayContent).topRunningActivity();
+ when(mDisplayContent.getIgnoreOrientationRequest()).thenReturn(true);
+
+ mMockLetterboxConfiguration = mock(LetterboxConfiguration.class);
+ when(mMockLetterboxConfiguration.isDisplayRotationImmersiveAppCompatPolicyEnabled(
+ /* checkDeviceConfig */ anyBoolean())).thenReturn(true);
+
+ mPolicy = DisplayRotationImmersiveAppCompatPolicy.createIfNeeded(
+ mMockLetterboxConfiguration, createDisplayRotationMock(),
+ mDisplayContent);
+ }
+
+ private DisplayRotation createDisplayRotationMock() {
+ DisplayRotation mockDisplayRotation = mock(DisplayRotation.class);
+
+ when(mockDisplayRotation.isAnyPortrait(Surface.ROTATION_0)).thenReturn(true);
+ when(mockDisplayRotation.isAnyPortrait(Surface.ROTATION_90)).thenReturn(false);
+ when(mockDisplayRotation.isAnyPortrait(Surface.ROTATION_180)).thenReturn(true);
+ when(mockDisplayRotation.isAnyPortrait(Surface.ROTATION_270)).thenReturn(false);
+ when(mockDisplayRotation.isLandscapeOrSeascape(Surface.ROTATION_0)).thenReturn(false);
+ when(mockDisplayRotation.isLandscapeOrSeascape(Surface.ROTATION_90)).thenReturn(true);
+ when(mockDisplayRotation.isLandscapeOrSeascape(Surface.ROTATION_180)).thenReturn(false);
+ when(mockDisplayRotation.isLandscapeOrSeascape(Surface.ROTATION_270)).thenReturn(true);
+
+ return mockDisplayRotation;
+ }
+
+ @Test
+ public void testIsRotationLockEnforced_landscapeActivity_lockedWhenRotatingToPortrait() {
+ // Base case: App is optimal in Landscape.
+
+ // ROTATION_* is the target display orientation counted from the natural display
+ // orientation. Outside of test environment, ROTATION_0 means that proposed display
+ // rotation is the natural device orientation.
+ // DisplayRotationImmersiveAppCompatPolicy assesses whether the proposed target
+ // orientation ROTATION_* is optimal for the top fullscreen activity or not.
+ // For instance, ROTATION_0 means portrait screen orientation (see
+ // createDisplayRotationMock) which isn't optimal for a landscape-only activity so
+ // we should show a rotation suggestion button instead of rotating directly.
+
+ // Rotation to portrait
+ assertTrue(mPolicy.isRotationLockEnforced(Surface.ROTATION_0));
+ // Rotation to landscape
+ assertFalse(mPolicy.isRotationLockEnforced(Surface.ROTATION_90));
+ // Rotation to portrait
+ assertTrue(mPolicy.isRotationLockEnforced(Surface.ROTATION_180));
+ // Rotation to landscape
+ assertFalse(mPolicy.isRotationLockEnforced(Surface.ROTATION_270));
+ }
+
+ @Test
+ public void testIsRotationLockEnforced_portraitActivity_lockedWhenRotatingToLandscape() {
+ when(mMockActivityRecord.getRequestedConfigurationOrientation()).thenReturn(
+ ORIENTATION_PORTRAIT);
+
+ // Rotation to portrait
+ assertFalse(mPolicy.isRotationLockEnforced(Surface.ROTATION_0));
+ // Rotation to landscape
+ assertTrue(mPolicy.isRotationLockEnforced(Surface.ROTATION_90));
+ // Rotation to portrait
+ assertFalse(mPolicy.isRotationLockEnforced(Surface.ROTATION_180));
+ // Rotation to landscape
+ assertTrue(mPolicy.isRotationLockEnforced(Surface.ROTATION_270));
+ }
+
+ @Test
+ public void testIsRotationLockEnforced_responsiveActivity_lockNotEnforced() {
+ // Do not fix screen orientation
+ when(mMockActivityRecord.getRequestedConfigurationOrientation()).thenReturn(
+ ORIENTATION_UNDEFINED);
+
+ assertIsRotationLockEnforcedReturnsFalseForAllRotations();
+ }
+
+ @Test
+ public void testIsRotationLockEnforced_statusBarVisible_lockNotEnforced() {
+ // Some system bars are visible
+ when(mMockWindowState.getRequestedVisibleTypes()).thenReturn(Type.statusBars());
+
+ assertIsRotationLockEnforcedReturnsFalseForAllRotations();
+ }
+
+ @Test
+ public void testIsRotationLockEnforced_navBarVisible_lockNotEnforced() {
+ // Some system bars are visible
+ when(mMockWindowState.getRequestedVisibleTypes()).thenReturn(Type.navigationBars());
+
+ assertIsRotationLockEnforcedReturnsFalseForAllRotations();
+ }
+
+ @Test
+ public void testIsRotationLockEnforced_activityIsLetterboxed_lockNotEnforced() {
+ // Activity is letterboxed
+ when(mMockActivityRecord.areBoundsLetterboxed()).thenReturn(true);
+
+ assertIsRotationLockEnforcedReturnsFalseForAllRotations();
+ }
+
+ @Test
+ public void testIsRotationLockEnforced_notFullscreen_lockNotEnforced() {
+ when(mMockTask.getWindowingMode()).thenReturn(WINDOWING_MODE_MULTI_WINDOW);
+
+ assertIsRotationLockEnforcedReturnsFalseForAllRotations();
+
+ when(mMockTask.getWindowingMode()).thenReturn(WINDOWING_MODE_PINNED);
+
+ assertIsRotationLockEnforcedReturnsFalseForAllRotations();
+
+ when(mMockTask.getWindowingMode()).thenReturn(WINDOWING_MODE_FREEFORM);
+
+ assertIsRotationLockEnforcedReturnsFalseForAllRotations();
+ }
+
+ @Test
+ public void testIsRotationLockEnforced_ignoreOrientationRequestDisabled_lockNotEnforced() {
+ when(mDisplayContent.getIgnoreOrientationRequest()).thenReturn(false);
+
+ assertIsRotationLockEnforcedReturnsFalseForAllRotations();
+ }
+
+ @Test
+ public void testRotationChoiceEnforcedOnly_nullTopRunningActivity_lockNotEnforced() {
+ when(mDisplayContent.topRunningActivity()).thenReturn(null);
+
+ assertIsRotationLockEnforcedReturnsFalseForAllRotations();
+ }
+
+ @Test
+ public void testRotationChoiceEnforcedOnly_featureFlagDisabled_lockNotEnforced() {
+ when(mMockLetterboxConfiguration.isDisplayRotationImmersiveAppCompatPolicyEnabled(
+ /* checkDeviceConfig */ true)).thenReturn(false);
+
+ assertIsRotationLockEnforcedReturnsFalseForAllRotations();
+ }
+
+ private void assertIsRotationLockEnforcedReturnsFalseForAllRotations() {
+ assertFalse(mPolicy.isRotationLockEnforced(Surface.ROTATION_0));
+ assertFalse(mPolicy.isRotationLockEnforced(Surface.ROTATION_90));
+ assertFalse(mPolicy.isRotationLockEnforced(Surface.ROTATION_180));
+ assertFalse(mPolicy.isRotationLockEnforced(Surface.ROTATION_270));
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index 491f876dceed..4ce43e1fc469 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -1096,8 +1096,16 @@ public class DisplayRotationTests {
mMockDisplayAddress = mock(DisplayAddress.class);
mMockDisplayWindowSettings = mock(DisplayWindowSettings.class);
+
mTarget = new DisplayRotation(sMockWm, mMockDisplayContent, mMockDisplayAddress,
- mMockDisplayPolicy, mMockDisplayWindowSettings, mMockContext, new Object());
+ mMockDisplayPolicy, mMockDisplayWindowSettings, mMockContext, new Object()) {
+ @Override
+ DisplayRotationImmersiveAppCompatPolicy initImmersiveAppCompatPolicy(
+ WindowManagerService service, DisplayContent displayContent) {
+ return null;
+ }
+ };
+
reset(sMockWm);
captureObservers();
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationDeviceConfigTests.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationDeviceConfigTests.java
new file mode 100644
index 000000000000..2b7a06bd35f3
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationDeviceConfigTests.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.LetterboxConfigurationDeviceConfig.sKeyToDefaultValueMap;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.Presubmit;
+import android.provider.DeviceConfig;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.modules.utils.testing.TestableDeviceConfig;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.Map;
+
+/**
+ * Test class for {@link LetterboxConfigurationDeviceConfig}.
+ *
+ * atest WmTests:LetterboxConfigurationDeviceConfigTests
+ */
+@SmallTest
+@Presubmit
+public class LetterboxConfigurationDeviceConfigTests {
+
+ private LetterboxConfigurationDeviceConfig mDeviceConfig;
+
+ @Rule
+ public final TestableDeviceConfig.TestableDeviceConfigRule
+ mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
+
+ @Before
+ public void setUp() {
+ mDeviceConfig = new LetterboxConfigurationDeviceConfig(/* executor */ Runnable::run);
+ }
+
+ @Test
+ public void testGetFlag_flagIsActive_flagChanges() throws Throwable {
+ for (Map.Entry<String, Boolean> entry : sKeyToDefaultValueMap.entrySet()) {
+ testGetFlagForKey_flagIsActive_flagChanges(entry.getKey(), entry.getValue());
+ }
+ }
+
+ private void testGetFlagForKey_flagIsActive_flagChanges(final String key, boolean defaultValue)
+ throws InterruptedException {
+ mDeviceConfig.updateFlagActiveStatus(/* isActive */ true, key);
+
+ assertEquals("Unexpected default value for " + key,
+ mDeviceConfig.getFlag(key), defaultValue);
+
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER, key,
+ /* value */ Boolean.TRUE.toString(), /* makeDefault */ false);
+
+ assertTrue("Flag " + key + "is not true after change", mDeviceConfig.getFlag(key));
+
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER, key,
+ /* value */ Boolean.FALSE.toString(), /* makeDefault */ false);
+
+ assertFalse("Flag " + key + "is not false after change", mDeviceConfig.getFlag(key));
+ }
+
+ @Test
+ public void testGetFlag_flagIsNotActive_alwaysReturnDefaultValue() throws Throwable {
+ for (Map.Entry<String, Boolean> entry : sKeyToDefaultValueMap.entrySet()) {
+ testGetFlagForKey_flagIsNotActive_alwaysReturnDefaultValue(
+ entry.getKey(), entry.getValue());
+ }
+ }
+
+ private void testGetFlagForKey_flagIsNotActive_alwaysReturnDefaultValue(final String key,
+ boolean defaultValue) throws InterruptedException {
+ assertEquals("Unexpected default value for " + key,
+ mDeviceConfig.getFlag(key), defaultValue);
+
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER, key,
+ /* value */ Boolean.TRUE.toString(), /* makeDefault */ false);
+
+ assertEquals("Flag " + key + "is not set to default after change",
+ mDeviceConfig.getFlag(key), defaultValue);
+
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER, key,
+ /* value */ Boolean.FALSE.toString(), /* makeDefault */ false);
+
+ assertEquals("Flag " + key + "is not set to default after change",
+ mDeviceConfig.getFlag(key), defaultValue);
+ }
+
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
new file mode 100644
index 000000000000..6d778afee88c
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyString;
+
+import android.compat.testing.PlatformCompatChangeRule;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.Property;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+ /**
+ * Test class for {@link LetterboxUiControllerTest}.
+ *
+ * Build/Install/Run:
+ * atest WmTests:LetterboxUiControllerTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class LetterboxUiControllerTest extends WindowTestsBase {
+
+ @Rule
+ public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
+ private ActivityRecord mActivity;
+ private DisplayContent mDisplayContent;
+ private LetterboxUiController mController;
+ private LetterboxConfiguration mLetterboxConfiguration;
+
+ @Before
+ public void setUp() throws Exception {
+ mActivity = setUpActivityWithComponent();
+
+ mLetterboxConfiguration = mWm.mLetterboxConfiguration;
+ spyOn(mLetterboxConfiguration);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION})
+ public void testShouldIgnoreRequestedOrientation_activityRelaunching_returnsTrue() {
+ prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch();
+
+ assertTrue(mController.shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED));
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION})
+ public void testShouldIgnoreRequestedOrientation_cameraCompatTreatment_returnsTrue() {
+ doReturn(true).when(mLetterboxConfiguration).isCameraCompatTreatmentEnabled(anyBoolean());
+
+ // Recreate DisplayContent with DisplayRotationCompatPolicy
+ mActivity = setUpActivityWithComponent();
+ mController = new LetterboxUiController(mWm, mActivity);
+ prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch();
+ mController.setRelauchingAfterRequestedOrientationChanged(false);
+
+ spyOn(mDisplayContent.mDisplayRotationCompatPolicy);
+ doReturn(true).when(mDisplayContent.mDisplayRotationCompatPolicy)
+ .isTreatmentEnabledForActivity(eq(mActivity));
+
+ assertTrue(mController.shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED));
+ }
+
+ @Test
+ public void testShouldIgnoreRequestedOrientation_overrideDisabled_returnsFalse() {
+ prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch();
+
+ assertFalse(mController.shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED));
+ }
+
+ @Test
+ public void testShouldIgnoreRequestedOrientation_propertyIsTrue_returnsTrue()
+ throws Exception {
+ doReturn(true).when(mLetterboxConfiguration)
+ .isPolicyForIgnoringRequestedOrientationEnabled();
+ mockThatProperty(PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION, /* value */ true);
+ mController = new LetterboxUiController(mWm, mActivity);
+ prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch();
+
+ assertTrue(mController.shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED));
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION})
+ public void testShouldIgnoreRequestedOrientation_propertyIsFalseAndOverride_returnsFalse()
+ throws Exception {
+ doReturn(true).when(mLetterboxConfiguration)
+ .isPolicyForIgnoringRequestedOrientationEnabled();
+ mockThatProperty(PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION, /* value */ false);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+ prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch();
+
+ assertFalse(mController.shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED));
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION})
+ public void testShouldIgnoreRequestedOrientation_flagIsDisabled_returnsFalse() {
+ prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch();
+ doReturn(false).when(mLetterboxConfiguration)
+ .isPolicyForIgnoringRequestedOrientationEnabled();
+
+ assertFalse(mController.shouldIgnoreRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED));
+ }
+
+ private void mockThatProperty(String propertyName, boolean value) throws Exception {
+ Property property = new Property(propertyName, /* value */ value, /* packageName */ "",
+ /* className */ "");
+ PackageManager pm = mWm.mContext.getPackageManager();
+ spyOn(pm);
+ doReturn(property).when(pm).getProperty(eq(propertyName), anyString());
+ }
+
+ private void prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch() {
+ doReturn(true).when(mLetterboxConfiguration)
+ .isPolicyForIgnoringRequestedOrientationEnabled();
+ mController.setRelauchingAfterRequestedOrientationChanged(true);
+ }
+
+ private ActivityRecord setUpActivityWithComponent() {
+ mDisplayContent = new TestDisplayContent
+ .Builder(mAtm, /* dw */ 1000, /* dh */ 2000).build();
+ Task task = new TaskBuilder(mSupervisor).setDisplay(mDisplayContent).build();
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setOnTop(true)
+ .setTask(task)
+ // Set the component to be that of the test class in order to enable compat changes
+ .setComponent(ComponentName.createRelative(mContext,
+ com.android.server.wm.LetterboxUiControllerTest.class.getName()))
+ .build();
+ return activity;
+ }
+}
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 367f91bc5bc3..d364dbbbaac0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -30,7 +30,6 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static android.os.Process.NOBODY_UID;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -1208,34 +1207,20 @@ public class RecentTasksTest extends WindowTestsBase {
@Test
public void testCreateRecentTaskInfo_detachedTask() {
- final Task task = createTaskBuilder(".Task").build();
- new ActivityBuilder(mSupervisor.mService)
- .setTask(task)
- .setUid(NOBODY_UID)
- .setComponent(getUniqueComponentName())
- .build();
+ final Task task = createTaskBuilder(".Task").setCreateActivity(true).build();
final TaskDisplayArea tda = task.getDisplayArea();
assertTrue(task.isAttached());
assertTrue(task.supportsMultiWindow());
- RecentTaskInfo info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
- true /* getTasksAllowed */);
+ RecentTaskInfo info = mRecentTasks.createRecentTaskInfo(task, true);
assertTrue(info.supportsMultiWindow);
- info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
- false /* getTasksAllowed */);
-
- assertTrue(info.topActivity == null);
- assertTrue(info.topActivityInfo == null);
- assertTrue(info.baseActivity == null);
-
// The task can be put in split screen even if it is not attached now.
task.removeImmediately();
- info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
- true /* getTasksAllowed */);
+ info = mRecentTasks.createRecentTaskInfo(task, true);
assertTrue(info.supportsMultiWindow);
@@ -1244,8 +1229,7 @@ public class RecentTasksTest extends WindowTestsBase {
doReturn(false).when(tda).supportsNonResizableMultiWindow();
doReturn(false).when(task).isResizeable();
- info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
- true /* getTasksAllowed */);
+ info = mRecentTasks.createRecentTaskInfo(task, true);
assertFalse(info.supportsMultiWindow);
@@ -1253,8 +1237,7 @@ public class RecentTasksTest extends WindowTestsBase {
// the device supports it.
doReturn(true).when(tda).supportsNonResizableMultiWindow();
- info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
- true /* getTasksAllowed */);
+ info = mRecentTasks.createRecentTaskInfo(task, true);
assertTrue(info.supportsMultiWindow);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
index 736f8f7a5ed3..1ee0959447a6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
@@ -48,7 +48,6 @@ import android.view.IWindowManager;
import android.view.PointerIcon;
import android.view.SurfaceControl;
import android.view.cts.surfacevalidator.BitmapPixelChecker;
-import android.view.cts.surfacevalidator.PixelColor;
import android.view.cts.surfacevalidator.SaveBitmapHelper;
import android.window.ScreenCapture;
import android.window.ScreenCapture.ScreenCaptureListener;
@@ -132,7 +131,7 @@ public class ScreenshotTests {
Bitmap swBitmap = screenshot.copy(Bitmap.Config.ARGB_8888, false);
screenshot.recycle();
- BitmapPixelChecker bitmapPixelChecker = new BitmapPixelChecker(PixelColor.RED);
+ BitmapPixelChecker bitmapPixelChecker = new BitmapPixelChecker(Color.RED);
Rect bounds = new Rect(0, 0, swBitmap.getWidth(), swBitmap.getHeight());
int numMatchingPixels = bitmapPixelChecker.getNumMatchingPixels(swBitmap, bounds);
int sizeOfBitmap = bounds.width() * bounds.height();
@@ -182,7 +181,7 @@ public class ScreenshotTests {
Bitmap swBitmap = screenshot.copy(Bitmap.Config.ARGB_8888, false);
screenshot.recycle();
- BitmapPixelChecker bitmapPixelChecker = new BitmapPixelChecker(PixelColor.RED);
+ BitmapPixelChecker bitmapPixelChecker = new BitmapPixelChecker(Color.RED);
Rect bounds = new Rect(point.x, point.y, BUFFER_WIDTH + point.x, BUFFER_HEIGHT + point.y);
int numMatchingPixels = bitmapPixelChecker.getNumMatchingPixels(swBitmap, bounds);
int pixelMatchSize = bounds.width() * bounds.height();
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 2cce62e1685c..a48a0bcafcf3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -94,6 +94,7 @@ import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
import android.view.InsetsFrameProvider;
import android.view.InsetsSource;
+import android.view.InsetsState;
import android.view.WindowManager;
import androidx.test.filters.MediumTest;
@@ -1466,6 +1467,12 @@ public class SizeCompatTests extends WindowTestsBase {
assertEquals(new Rect(notchHeight, 0, 0, 0), mActivity.getLetterboxInsets());
assertTrue(displayPolicy.isFullyTransparentAllowed(w, ITYPE_STATUS_BAR));
assertActivityMaxBoundsSandboxed();
+
+ // The insets state for metrics should be rotated (landscape).
+ final InsetsState insetsState = new InsetsState();
+ mActivity.mDisplayContent.getInsetsPolicy().getInsetsForWindowMetrics(
+ mActivity, insetsState);
+ assertEquals(dh, insetsState.getDisplayFrame().width());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 26fe5214a7ea..b70d8bd50917 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -23,6 +23,7 @@ import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE;
import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE;
@@ -73,6 +74,7 @@ import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
+import android.graphics.Color;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Binder;
@@ -83,8 +85,10 @@ import android.platform.test.annotations.Presubmit;
import android.view.RemoteAnimationDefinition;
import android.view.SurfaceControl;
import android.window.ITaskFragmentOrganizer;
+import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentCreationParams;
import android.window.TaskFragmentInfo;
+import android.window.TaskFragmentOperation;
import android.window.TaskFragmentOrganizer;
import android.window.TaskFragmentOrganizerToken;
import android.window.TaskFragmentParentInfo;
@@ -689,6 +693,59 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
}
@Test
+ public void testApplyTransaction_enforceTaskFragmentOrganized_setTaskFragmentOperation() {
+ final Task task = createTask(mDisplayContent);
+ mTaskFragment = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .setFragmentToken(mFragmentToken)
+ .build();
+ mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
+ final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+ OP_TYPE_SET_ANIMATION_PARAMS)
+ .setAnimationParams(TaskFragmentAnimationParams.DEFAULT)
+ .build();
+ mTransaction.setTaskFragmentOperation(mFragmentToken, operation);
+ mOrganizer.applyTransaction(mTransaction, TASK_FRAGMENT_TRANSIT_CHANGE,
+ false /* shouldApplyIndependently */);
+
+ // Not allowed because TaskFragment is not organized by the caller organizer.
+ assertApplyTransactionDisallowed(mTransaction);
+
+ mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
+ "Test:TaskFragmentOrganizer" /* processName */);
+
+ assertApplyTransactionAllowed(mTransaction);
+ }
+
+ @Test
+ public void testSetTaskFragmentOperation() {
+ final Task task = createTask(mDisplayContent);
+ mTaskFragment = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .setOrganizer(mOrganizer)
+ .setFragmentToken(mFragmentToken)
+ .build();
+ assertEquals(TaskFragmentAnimationParams.DEFAULT, mTaskFragment.getAnimationParams());
+
+ mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
+ final TaskFragmentAnimationParams animationParams =
+ new TaskFragmentAnimationParams.Builder()
+ .setAnimationBackgroundColor(Color.GREEN)
+ .build();
+ final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+ OP_TYPE_SET_ANIMATION_PARAMS)
+ .setAnimationParams(animationParams)
+ .build();
+ mTransaction.setTaskFragmentOperation(mFragmentToken, operation);
+ mOrganizer.applyTransaction(mTransaction, TASK_FRAGMENT_TRANSIT_CHANGE,
+ false /* shouldApplyIndependently */);
+ assertApplyTransactionAllowed(mTransaction);
+
+ assertEquals(animationParams, mTaskFragment.getAnimationParams());
+ assertEquals(Color.GREEN, mTaskFragment.getAnimationParams().getAnimationBackgroundColor());
+ }
+
+ @Test
public void testApplyTransaction_createTaskFragment_failForDifferentUid() {
final ActivityRecord activity = createActivityRecord(mDisplayContent);
final int uid = Binder.getCallingUid();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 3d777f81579b..cac7745a5e86 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -214,7 +214,7 @@ public class WindowManagerServiceTests extends WindowTestsBase {
final MergedConfiguration outConfig = new MergedConfiguration();
final SurfaceControl outSurfaceControl = new SurfaceControl();
final InsetsState outInsetsState = new InsetsState();
- final InsetsSourceControl[] outControls = new InsetsSourceControl[0];
+ final InsetsSourceControl.Array outControls = new InsetsSourceControl.Array();
final Bundle outBundle = new Bundle();
mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.GONE, 0, 0, 0,
outFrames, outConfig, outSurfaceControl, outInsetsState, outControls, outBundle);
@@ -351,7 +351,7 @@ public class WindowManagerServiceTests extends WindowTestsBase {
mWm.addWindow(session, new TestIWindow(), params, View.VISIBLE, DEFAULT_DISPLAY,
UserHandle.USER_SYSTEM, WindowInsets.Type.defaultVisible(), null, new InsetsState(),
- new InsetsSourceControl[0], new Rect(), new float[1]);
+ new InsetsSourceControl.Array(), new Rect(), new float[1]);
verify(mWm.mWindowContextListenerController, never()).registerWindowContainerListener(any(),
any(), anyInt(), anyInt(), any());
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
index 689423ad3e3e..4d6d3205db40 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
@@ -93,21 +93,27 @@ import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
/**
- * A class that provides trusted hotword detector to communicate with the {@link
- * HotwordDetectionService}.
+ * A class that provides sandboxed detector to communicate with the {@link
+ * HotwordDetectionService} and {@link VisualQueryDetectionService}.
*
- * This class provides the methods to do initialization with the {@link HotwordDetectionService}
- * and handle external source detection. It also provides the methods to check if we can egress
- * the data from the {@link HotwordDetectionService}.
+ * Trusted hotword detectors such as {@link SoftwareHotwordDetector} and
+ * {@link AlwaysOnHotwordDetector} will leverage this class to communitcate with
+ * {@link HotwordDetectionService}; similarly, {@link VisualQueryDetector} will communicate with
+ * {@link VisualQueryDetectionService}.
+ *
+ * This class provides the methods to do initialization with the {@link HotwordDetectionService} and
+ * {@link VisualQueryDetectionService} handles external source detection for
+ * {@link HotwordDetectionService}. It also provides the methods to check if we can egress the data
+ * from the {@link HotwordDetectionService} and {@link VisualQueryDetectionService}.
*
* The subclass should override the {@link #informRestartProcessLocked()} to handle the trusted
* process restart.
*/
-abstract class HotwordDetectorSession {
- private static final String TAG = "HotwordDetectorSession";
+abstract class DetectorSession {
+ private static final String TAG = "DetectorSession";
static final boolean DEBUG = false;
- private static final String OP_MESSAGE =
+ private static final String HOTWORD_DETECTION_OP_MESSAGE =
"Providing hotword detection result to VoiceInteractionService";
// The error codes are used for onError callback
@@ -173,7 +179,7 @@ abstract class HotwordDetectorSession {
@GuardedBy("mLock")
ParcelFileDescriptor mCurrentAudioSink;
@GuardedBy("mLock")
- @NonNull HotwordDetectionConnection.ServiceConnection mRemoteHotwordDetectionService;
+ @NonNull HotwordDetectionConnection.ServiceConnection mRemoteDetectionService;
boolean mDebugHotwordLogging = false;
@GuardedBy("mLock")
private double mProximityMeters = PROXIMITY_UNKNOWN;
@@ -185,13 +191,13 @@ abstract class HotwordDetectorSession {
boolean mPerformingExternalSourceHotwordDetection;
@NonNull final IBinder mToken;
- HotwordDetectorSession(
- @NonNull HotwordDetectionConnection.ServiceConnection remoteHotwordDetectionService,
+ DetectorSession(
+ @NonNull HotwordDetectionConnection.ServiceConnection remoteDetectionService,
@NonNull Object lock, @NonNull Context context, @NonNull IBinder token,
@NonNull IHotwordRecognitionStatusCallback callback, int voiceInteractionServiceUid,
Identity voiceInteractorIdentity,
@NonNull ScheduledExecutorService scheduledExecutorService, boolean logging) {
- mRemoteHotwordDetectionService = remoteHotwordDetectionService;
+ mRemoteDetectionService = remoteDetectionService;
mLock = lock;
mContext = context;
mToken = token;
@@ -219,7 +225,7 @@ abstract class HotwordDetectorSession {
if (DEBUG) {
Slog.d(TAG, "updateStateAfterProcessStartLocked");
}
- AndroidFuture<Void> voidFuture = mRemoteHotwordDetectionService.postAsync(service -> {
+ AndroidFuture<Void> voidFuture = mRemoteDetectionService.postAsync(service -> {
AndroidFuture<Void> future = new AndroidFuture<>();
IRemoteCallback statusCallback = new IRemoteCallback.Stub() {
@Override
@@ -319,7 +325,7 @@ abstract class HotwordDetectorSession {
Slog.v(TAG, "call updateStateAfterProcessStartLocked");
updateStateAfterProcessStartLocked(options, sharedMemory);
} else {
- mRemoteHotwordDetectionService.run(
+ mRemoteDetectionService.run(
service -> service.updateState(options, sharedMemory, /* callback= */ null));
}
}
@@ -407,7 +413,7 @@ abstract class HotwordDetectorSession {
// TODO: handle cancellations well
// TODO: what if we cancelled and started a new one?
- mRemoteHotwordDetectionService.run(
+ mRemoteDetectionService.run(
service -> {
service.detectFromMicrophoneSource(
serviceAudioSource,
@@ -512,7 +518,7 @@ abstract class HotwordDetectorSession {
void destroyLocked() {
mDestroyed = true;
mDebugHotwordLogging = false;
- mRemoteHotwordDetectionService = null;
+ mRemoteDetectionService = null;
if (mAttentionManagerInternal != null) {
mAttentionManagerInternal.onStopProximityUpdates(mProximityCallbackInternal);
}
@@ -524,9 +530,9 @@ abstract class HotwordDetectorSession {
}
@SuppressWarnings("GuardedBy")
- void updateRemoteHotwordDetectionServiceLocked(
- @NonNull HotwordDetectionConnection.ServiceConnection remoteHotwordDetectionService) {
- mRemoteHotwordDetectionService = remoteHotwordDetectionService;
+ void updateRemoteSandboxedDetectionServiceLocked(
+ @NonNull HotwordDetectionConnection.ServiceConnection remoteDetectionService) {
+ mRemoteDetectionService = remoteDetectionService;
}
void reportErrorLocked(int status) {
@@ -628,9 +634,9 @@ abstract class HotwordDetectorSession {
int hotwordOp = AppOpsManager.strOpToOp(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD);
mAppOpsManager.noteOpNoThrow(hotwordOp,
mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
- mVoiceInteractorIdentity.attributionTag, OP_MESSAGE);
+ mVoiceInteractorIdentity.attributionTag, HOTWORD_DETECTION_OP_MESSAGE);
enforcePermissionForDataDelivery(mContext, mVoiceInteractorIdentity,
- CAPTURE_AUDIO_HOTWORD, OP_MESSAGE);
+ CAPTURE_AUDIO_HOTWORD, HOTWORD_DETECTION_OP_MESSAGE);
}
});
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
index 84bd71601643..ad84f004e966 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
@@ -32,7 +32,6 @@ import android.os.IBinder;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.SharedMemory;
-import android.service.voice.AlwaysOnHotwordDetector;
import android.service.voice.HotwordDetectedResult;
import android.service.voice.HotwordDetectionService;
import android.service.voice.HotwordDetector;
@@ -59,7 +58,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
* {@link android.service.voice.VoiceInteractionService#createAlwaysOnHotwordDetector(String,
* Locale, PersistableBundle, SharedMemory, AlwaysOnHotwordDetector.Callback)}.
*/
-final class DspTrustedHotwordDetectorSession extends HotwordDetectorSession {
+final class DspTrustedHotwordDetectorSession extends DetectorSession {
private static final String TAG = "DspTrustedHotwordDetectorSession";
// The validation timeout value is 3 seconds for onDetect of DSP trigger event.
@@ -182,7 +181,7 @@ final class DspTrustedHotwordDetectorSession extends HotwordDetectorSession {
};
mValidatingDspTrigger = true;
- mRemoteHotwordDetectionService.run(service -> {
+ mRemoteDetectionService.run(service -> {
// We use the VALIDATION_TIMEOUT_MILLIS to inform that the client needs to invoke
// the callback before timeout value. In order to reduce the latency impact between
// server side and client side, we need to use another timeout value
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 276bccdb9f23..d501af7d83be 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -89,7 +89,7 @@ final class HotwordDetectionConnection {
Executors.newSingleThreadScheduledExecutor();
@Nullable private final ScheduledFuture<?> mCancellationTaskFuture;
private final IBinder.DeathRecipient mAudioServerDeathRecipient = this::audioServerDied;
- @NonNull private final ServiceConnectionFactory mServiceConnectionFactory;
+ @NonNull private final ServiceConnectionFactory mHotwordDetectionServiceConnectionFactory;
private final int mDetectorType;
/**
* Time after which each HotwordDetectionService process is stopped and replaced by a new one.
@@ -99,7 +99,7 @@ final class HotwordDetectionConnection {
final Object mLock;
final int mVoiceInteractionServiceUid;
- final ComponentName mDetectionComponentName;
+ final ComponentName mHotwordDetectionComponentName;
final int mUser;
final Context mContext;
volatile HotwordDetectionServiceIdentity mIdentity;
@@ -122,27 +122,30 @@ final class HotwordDetectionConnection {
* to record the detectors.
*/
@GuardedBy("mLock")
- private final SparseArray<HotwordDetectorSession> mHotwordDetectorSessions =
+ private final SparseArray<DetectorSession> mDetectorSessions =
new SparseArray<>();
HotwordDetectionConnection(Object lock, Context context, int voiceInteractionServiceUid,
- Identity voiceInteractorIdentity, ComponentName serviceName, int userId,
+ Identity voiceInteractorIdentity, ComponentName hotwordDetectionServiceName, int userId,
boolean bindInstantServiceAllowed, int detectorType) {
mLock = lock;
mContext = context;
mVoiceInteractionServiceUid = voiceInteractionServiceUid;
mVoiceInteractorIdentity = voiceInteractorIdentity;
- mDetectionComponentName = serviceName;
+ mHotwordDetectionComponentName = hotwordDetectionServiceName;
mUser = userId;
mDetectorType = detectorType;
mReStartPeriodSeconds = DeviceConfig.getInt(DeviceConfig.NAMESPACE_VOICE_INTERACTION,
KEY_RESTART_PERIOD_IN_SECONDS, 0);
- final Intent intent = new Intent(HotwordDetectionService.SERVICE_INTERFACE);
- intent.setComponent(mDetectionComponentName);
+ final Intent hotwordDetectionServiceIntent =
+ new Intent(HotwordDetectionService.SERVICE_INTERFACE);
+ hotwordDetectionServiceIntent.setComponent(mHotwordDetectionComponentName);
initAudioFlingerLocked();
- mServiceConnectionFactory = new ServiceConnectionFactory(intent, bindInstantServiceAllowed);
- mRemoteHotwordDetectionService = mServiceConnectionFactory.createLocked();
+ mHotwordDetectionServiceConnectionFactory =
+ new ServiceConnectionFactory(hotwordDetectionServiceIntent,
+ bindInstantServiceAllowed);
+ mRemoteHotwordDetectionService = mHotwordDetectionServiceConnectionFactory.createLocked();
mLastRestartInstant = Instant.now();
if (mReStartPeriodSeconds <= 0) {
@@ -176,8 +179,8 @@ final class HotwordDetectionConnection {
try {
mAudioFlinger.linkToDeath(mAudioServerDeathRecipient, /* flags= */ 0);
} catch (RemoteException e) {
- Slog.w(TAG, "Audio server died before we registered a DeathRecipient; retrying init.",
- e);
+ Slog.w(TAG, "Audio server died before we registered a DeathRecipient; "
+ + "retrying init.", e);
initAudioFlingerLocked();
}
}
@@ -200,10 +203,10 @@ final class HotwordDetectionConnection {
void cancelLocked() {
Slog.v(TAG, "cancelLocked");
clearDebugHotwordLoggingTimeoutLocked();
- runForEachHotwordDetectorSessionLocked((session) -> {
+ runForEachDetectorSessionLocked((session) -> {
session.destroyLocked();
});
- mHotwordDetectorSessions.clear();
+ mDetectorSessions.clear();
mDebugHotwordLogging = false;
mRemoteHotwordDetectionService.unbind();
LocalServices.getService(PermissionManagerServiceInternal.class)
@@ -223,7 +226,7 @@ final class HotwordDetectionConnection {
@SuppressWarnings("GuardedBy")
void updateStateLocked(PersistableBundle options, SharedMemory sharedMemory,
@NonNull IBinder token) {
- final HotwordDetectorSession session = getDetectorSessionByTokenLocked(token);
+ final DetectorSession session = getDetectorSessionByTokenLocked(token);
if (session == null) {
Slog.v(TAG, "Not found the detector by token");
return;
@@ -259,7 +262,7 @@ final class HotwordDetectionConnection {
if (DEBUG) {
Slog.d(TAG, "startListeningFromExternalSourceLocked");
}
- final HotwordDetectorSession session = getDetectorSessionByTokenLocked(token);
+ final DetectorSession session = getDetectorSessionByTokenLocked(token);
if (session == null) {
Slog.v(TAG, "Not found the detector by token");
return;
@@ -321,7 +324,7 @@ final class HotwordDetectionConnection {
Slog.v(TAG, "setDebugHotwordLoggingLocked: " + logging);
clearDebugHotwordLoggingTimeoutLocked();
mDebugHotwordLogging = logging;
- runForEachHotwordDetectorSessionLocked((session) -> {
+ runForEachDetectorSessionLocked((session) -> {
session.setDebugHotwordLoggingLocked(logging);
});
@@ -331,7 +334,7 @@ final class HotwordDetectionConnection {
Slog.v(TAG, "Timeout to reset mDebugHotwordLogging to false");
synchronized (mLock) {
mDebugHotwordLogging = false;
- runForEachHotwordDetectorSessionLocked((session) -> {
+ runForEachDetectorSessionLocked((session) -> {
session.setDebugHotwordLoggingLocked(false);
});
}
@@ -350,24 +353,24 @@ final class HotwordDetectionConnection {
private void restartProcessLocked() {
// TODO(b/244598068): Check HotwordAudioStreamManager first
Slog.v(TAG, "Restarting hotword detection process");
- ServiceConnection oldConnection = mRemoteHotwordDetectionService;
+ ServiceConnection oldHotwordConnection = mRemoteHotwordDetectionService;
HotwordDetectionServiceIdentity previousIdentity = mIdentity;
mLastRestartInstant = Instant.now();
// Recreate connection to reset the cache.
- mRemoteHotwordDetectionService = mServiceConnectionFactory.createLocked();
+ mRemoteHotwordDetectionService = mHotwordDetectionServiceConnectionFactory.createLocked();
Slog.v(TAG, "Started the new process, dispatching processRestarted to detector");
- runForEachHotwordDetectorSessionLocked((session) -> {
- session.updateRemoteHotwordDetectionServiceLocked(mRemoteHotwordDetectionService);
+ runForEachDetectorSessionLocked((session) -> {
+ session.updateRemoteSandboxedDetectionServiceLocked(mRemoteHotwordDetectionService);
session.informRestartProcessLocked();
});
if (DEBUG) {
Slog.i(TAG, "processRestarted is dispatched done, unbinding from the old process");
}
- oldConnection.ignoreConnectionStatusEvents();
- oldConnection.unbind();
+ oldHotwordConnection.ignoreConnectionStatusEvents();
+ oldHotwordConnection.unbind();
if (previousIdentity != null) {
removeServiceUidForAudioPolicy(previousIdentity.getIsolatedUid());
}
@@ -437,12 +440,12 @@ final class HotwordDetectionConnection {
pw.print(prefix); pw.print("mBound=");
pw.println(mRemoteHotwordDetectionService.isBound());
pw.print(prefix); pw.print("mRestartCount=");
- pw.println(mServiceConnectionFactory.mRestartCount);
+ pw.println(mHotwordDetectionServiceConnectionFactory.mRestartCount);
pw.print(prefix); pw.print("mLastRestartInstant="); pw.println(mLastRestartInstant);
pw.print(prefix); pw.print("mDetectorType=");
pw.println(HotwordDetector.detectorTypeToString(mDetectorType));
- pw.print(prefix); pw.println("HotwordDetectorSession(s)");
- runForEachHotwordDetectorSessionLocked((session) -> {
+ pw.print(prefix); pw.println("DetectorSession(s)");
+ runForEachDetectorSessionLocked((session) -> {
session.dumpLocked(prefix, pw);
});
}
@@ -537,9 +540,9 @@ final class HotwordDetectionConnection {
}
}
synchronized (HotwordDetectionConnection.this.mLock) {
- runForEachHotwordDetectorSessionLocked((session) -> {
+ runForEachDetectorSessionLocked((session) -> {
session.reportErrorLocked(
- HotwordDetectorSession.HOTWORD_DETECTION_SERVICE_DIED);
+ DetectorSession.HOTWORD_DETECTION_SERVICE_DIED);
});
}
// Can improve to log exit reason if needed
@@ -599,12 +602,12 @@ final class HotwordDetectionConnection {
int detectorType) {
// We only support one Dsp trusted hotword detector and one software hotword detector at
// the same time, remove existing one.
- HotwordDetectorSession removeSession = mHotwordDetectorSessions.get(detectorType);
+ DetectorSession removeSession = mDetectorSessions.get(detectorType);
if (removeSession != null) {
removeSession.destroyLocked();
- mHotwordDetectorSessions.remove(detectorType);
+ mDetectorSessions.remove(detectorType);
}
- final HotwordDetectorSession session;
+ final DetectorSession session;
if (detectorType == HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP) {
session = new DspTrustedHotwordDetectorSession(mRemoteHotwordDetectionService,
mLock, mContext, token, callback, mVoiceInteractionServiceUid,
@@ -615,30 +618,30 @@ final class HotwordDetectionConnection {
mVoiceInteractionServiceUid, mVoiceInteractorIdentity,
mScheduledExecutorService, mDebugHotwordLogging);
}
- mHotwordDetectorSessions.put(detectorType, session);
+ mDetectorSessions.put(detectorType, session);
session.initialize(options, sharedMemory);
}
@SuppressWarnings("GuardedBy")
void destroyDetectorLocked(@NonNull IBinder token) {
- final HotwordDetectorSession session = getDetectorSessionByTokenLocked(token);
+ final DetectorSession session = getDetectorSessionByTokenLocked(token);
if (session != null) {
session.destroyLocked();
- final int index = mHotwordDetectorSessions.indexOfValue(session);
- if (index < 0 || index > mHotwordDetectorSessions.size() - 1) {
+ final int index = mDetectorSessions.indexOfValue(session);
+ if (index < 0 || index > mDetectorSessions.size() - 1) {
return;
}
- mHotwordDetectorSessions.removeAt(index);
+ mDetectorSessions.removeAt(index);
}
}
@SuppressWarnings("GuardedBy")
- private HotwordDetectorSession getDetectorSessionByTokenLocked(IBinder token) {
+ private DetectorSession getDetectorSessionByTokenLocked(IBinder token) {
if (token == null) {
return null;
}
- for (int i = 0; i < mHotwordDetectorSessions.size(); i++) {
- final HotwordDetectorSession session = mHotwordDetectorSessions.valueAt(i);
+ for (int i = 0; i < mDetectorSessions.size(); i++) {
+ final DetectorSession session = mDetectorSessions.valueAt(i);
if (!session.isDestroyed() && session.isSameToken(token)) {
return session;
}
@@ -648,7 +651,7 @@ final class HotwordDetectionConnection {
@SuppressWarnings("GuardedBy")
private DspTrustedHotwordDetectorSession getDspTrustedHotwordDetectorSessionLocked() {
- final HotwordDetectorSession session = mHotwordDetectorSessions.get(
+ final DetectorSession session = mDetectorSessions.get(
HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP);
if (session == null || session.isDestroyed()) {
Slog.v(TAG, "Not found the Dsp detector");
@@ -659,7 +662,7 @@ final class HotwordDetectionConnection {
@SuppressWarnings("GuardedBy")
private SoftwareTrustedHotwordDetectorSession getSoftwareTrustedHotwordDetectorSessionLocked() {
- final HotwordDetectorSession session = mHotwordDetectorSessions.get(
+ final DetectorSession session = mDetectorSessions.get(
HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE);
if (session == null || session.isDestroyed()) {
Slog.v(TAG, "Not found the software detector");
@@ -669,10 +672,10 @@ final class HotwordDetectionConnection {
}
@SuppressWarnings("GuardedBy")
- private void runForEachHotwordDetectorSessionLocked(
- @NonNull Consumer<HotwordDetectorSession> action) {
- for (int i = 0; i < mHotwordDetectorSessions.size(); i++) {
- HotwordDetectorSession session = mHotwordDetectorSessions.valueAt(i);
+ private void runForEachDetectorSessionLocked(
+ @NonNull Consumer<DetectorSession> action) {
+ for (int i = 0; i < mDetectorSessions.size(); i++) {
+ DetectorSession session = mDetectorSessions.valueAt(i);
action.accept(session);
}
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java
index 4eb997a610a4..3ad963d21943 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java
@@ -55,7 +55,7 @@ import java.util.concurrent.ScheduledExecutorService;
* {@link android.service.voice.VoiceInteractionService#createHotwordDetector(PersistableBundle,
* SharedMemory, HotwordDetector.Callback)}.
*/
-final class SoftwareTrustedHotwordDetectorSession extends HotwordDetectorSession {
+final class SoftwareTrustedHotwordDetectorSession extends DetectorSession {
private static final String TAG = "SoftwareTrustedHotwordDetectorSession";
private IMicrophoneHotwordDetectionVoiceInteractionCallback mSoftwareCallback;
@@ -155,7 +155,7 @@ final class SoftwareTrustedHotwordDetectorSession extends HotwordDetectorSession
}
};
- mRemoteHotwordDetectionService.run(
+ mRemoteDetectionService.run(
service -> service.detectFromMicrophoneSource(
null,
AUDIO_SOURCE_MICROPHONE,
@@ -179,7 +179,7 @@ final class SoftwareTrustedHotwordDetectorSession extends HotwordDetectorSession
}
mPerformingSoftwareHotwordDetection = false;
- mRemoteHotwordDetectionService.run(ISandboxedDetectionService::stopDetection);
+ mRemoteDetectionService.run(ISandboxedDetectionService::stopDetection);
closeExternalAudioStreamLocked("stopping requested");
}
diff --git a/telephony/java/android/telephony/CellBroadcastIdRange.java b/telephony/java/android/telephony/CellBroadcastIdRange.java
index eaf4f1c4bf41..abee80f76f83 100644
--- a/telephony/java/android/telephony/CellBroadcastIdRange.java
+++ b/telephony/java/android/telephony/CellBroadcastIdRange.java
@@ -15,6 +15,7 @@
*/
package android.telephony;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
@@ -29,7 +30,9 @@ import java.util.Objects;
@SystemApi
public final class CellBroadcastIdRange implements Parcelable {
+ @IntRange(from = 0, to = 0xFFFF)
private int mStartId;
+ @IntRange(from = 0, to = 0xFFFF)
private int mEndId;
private int mType;
private boolean mIsEnabled;
@@ -38,18 +41,19 @@ public final class CellBroadcastIdRange implements Parcelable {
* Create a new CellBroacastRange
*
* @param startId first message identifier as specified in TS 23.041 (3GPP)
- * or C.R1001-G (3GPP2)
+ * or C.R1001-G (3GPP2). The value must be between 0 and 0xFFFF.
* @param endId last message identifier as specified in TS 23.041 (3GPP)
- * or C.R1001-G (3GPP2)
+ * or C.R1001-G (3GPP2). The value must be between 0 and 0xFFFF.
* @param type the message format as defined in {@link SmsCbMessage}
* @param isEnabled whether the range is enabled
*
* @throws IllegalArgumentException if endId < startId or invalid value
*/
- public CellBroadcastIdRange(int startId, int endId,
+ public CellBroadcastIdRange(@IntRange(from = 0, to = 0xFFFF) int startId,
+ @IntRange(from = 0, to = 0xFFFF) int endId,
@android.telephony.SmsCbMessage.MessageFormat int type, boolean isEnabled)
throws IllegalArgumentException {
- if (startId < 0 || endId < 0) {
+ if (startId < 0 || endId < 0 || startId > 0xFFFF || endId > 0xFFFF) {
throw new IllegalArgumentException("invalid id");
}
if (endId < startId) {
@@ -65,6 +69,7 @@ public final class CellBroadcastIdRange implements Parcelable {
* Return the first message identifier of this range as specified in TS 23.041 (3GPP)
* or C.R1001-G (3GPP2)
*/
+ @IntRange(from = 0, to = 0xFFFF)
public int getStartId() {
return mStartId;
}
@@ -73,6 +78,7 @@ public final class CellBroadcastIdRange implements Parcelable {
* Return the last message identifier of this range as specified in TS 23.041 (3GPP)
* or C.R1001-G (3GPP2)
*/
+ @IntRange(from = 0, to = 0xFFFF)
public int getEndId() {
return mEndId;
}
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index e6f134921cd9..a2a110d3f758 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -2014,7 +2014,7 @@ public final class SmsManager {
* @see #disableCellBroadcastRange(int, int, int)
*
* @throws IllegalArgumentException if endMessageId < startMessageId
- * @deprecated Use {@link TelephonyManager#setCellBroadcastRanges} instead.
+ * @deprecated Use {@link TelephonyManager#setCellBroadcastIdRanges} instead.
* {@hide}
*/
@Deprecated
@@ -2076,7 +2076,7 @@ public final class SmsManager {
* @see #enableCellBroadcastRange(int, int, int)
*
* @throws IllegalArgumentException if endMessageId < startMessageId
- * @deprecated Use {@link TelephonyManager#setCellBroadcastRanges} instead.
+ * @deprecated Use {@link TelephonyManager#setCellBroadcastIdRanges} instead.
* {@hide}
*/
@Deprecated
@@ -3486,7 +3486,7 @@ public final class SmsManager {
/**
* Reset all cell broadcast ranges. Previously enabled ranges will become invalid after this.
- * @deprecated Use {@link TelephonyManager#resetAllCellBroadcastRanges} instead
+ * @deprecated Use {@link TelephonyManager#setCellBroadcastIdRanges} with empty list instead
* @hide
*/
@Deprecated
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 059f4c355437..0ad5ba0eaa47 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -17851,12 +17851,12 @@ public class TelephonyManager {
/** @hide */
@Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = {"CELLBROADCAST_RESULT_"}, value = {
- CELLBROADCAST_RESULT_UNKNOWN,
- CELLBROADCAST_RESULT_SUCCESS,
- CELLBROADCAST_RESULT_UNSUPPORTED,
- CELLBROADCAST_RESULT_FAIL_CONFIG,
- CELLBROADCAST_RESULT_FAIL_ACTIVATION})
+ @IntDef(prefix = {"CELL_BROADCAST_RESULT_"}, value = {
+ CELL_BROADCAST_RESULT_UNKNOWN,
+ CELL_BROADCAST_RESULT_SUCCESS,
+ CELL_BROADCAST_RESULT_UNSUPPORTED,
+ CELL_BROADCAST_RESULT_FAIL_CONFIG,
+ CELL_BROADCAST_RESULT_FAIL_ACTIVATION})
public @interface CellBroadcastResult {}
/**
@@ -17864,35 +17864,35 @@ public class TelephonyManager {
* @hide
*/
@SystemApi
- public static final int CELLBROADCAST_RESULT_UNKNOWN = -1;
+ public static final int CELL_BROADCAST_RESULT_UNKNOWN = -1;
/**
* The cell broadcast request is successful.
* @hide
*/
@SystemApi
- public static final int CELLBROADCAST_RESULT_SUCCESS = 0;
+ public static final int CELL_BROADCAST_RESULT_SUCCESS = 0;
/**
* The cell broadcast request is not supported.
* @hide
*/
@SystemApi
- public static final int CELLBROADCAST_RESULT_UNSUPPORTED = 1;
+ public static final int CELL_BROADCAST_RESULT_UNSUPPORTED = 1;
/**
* The cell broadcast request is failed due to the error to set config
* @hide
*/
@SystemApi
- public static final int CELLBROADCAST_RESULT_FAIL_CONFIG = 2;
+ public static final int CELL_BROADCAST_RESULT_FAIL_CONFIG = 2;
/**
* The cell broadcast request is failed due to the error to set activation
* @hide
*/
@SystemApi
- public static final int CELLBROADCAST_RESULT_FAIL_ACTIVATION = 3;
+ public static final int CELL_BROADCAST_RESULT_FAIL_ACTIVATION = 3;
/**
* Set reception of cell broadcast messages with the list of the given ranges
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
index 566ec9ab0620..64ed45307e8d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -102,8 +102,8 @@ constructor(
}
/**
- * Checks that the [ComponentNameMatcher.NAV_BAR] window is visible at the start and end of
- * the transition
+ * Checks that the [ComponentNameMatcher.NAV_BAR] window is visible at the start and end of the
+ * transition
*
* Note: Phones only
*/
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
index 197564a6039a..c2526d3fff58 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
@@ -18,7 +18,7 @@ package com.android.server.wm.flicker.ime
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
-import android.platform.test.annotations.RequiresDevice
+import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
index 209eb0c22b62..b05beba696e9 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
@@ -17,7 +17,7 @@
package com.android.server.wm.flicker.ime
import android.platform.test.annotations.Presubmit
-import android.platform.test.annotations.RequiresDevice
+import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt
index 14a6668fdc9a..4ca9d5fa90e3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt
@@ -16,7 +16,7 @@
package com.android.server.wm.flicker.launch
-import android.platform.test.annotations.RequiresDevice
+import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.FlickerTestFactory
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt
index 99574ef832b6..a9f9204ded34 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt
@@ -17,7 +17,7 @@
package com.android.server.wm.flicker.launch
import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.RequiresDevice
+import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
index e0df5be9bac1..242f4576d808 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
@@ -17,7 +17,7 @@
package com.android.server.wm.flicker.launch
import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.RequiresDevice
+import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.FlickerTestFactory
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
index 66af72ede90c..a4f09c000963 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
@@ -18,7 +18,7 @@ package com.android.server.wm.flicker.launch
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
-import android.platform.test.annotations.RequiresDevice
+import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.FlickerTestFactory
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
index 26898f8b3ae6..56d7d5e133de 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
@@ -19,7 +19,7 @@ package com.android.server.wm.flicker.launch
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
-import android.platform.test.annotations.RequiresDevice
+import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.FlickerTestFactory
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
index c44ad83755f8..60b0f9b50a23 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
@@ -18,7 +18,7 @@ package com.android.server.wm.flicker.launch
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
-import android.platform.test.annotations.RequiresDevice
+import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.FlickerTestFactory
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt
index e3ffb45dbf42..52ca7a2e0612 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt
@@ -19,7 +19,7 @@ package com.android.server.wm.flicker.launch
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
-import android.platform.test.annotations.RequiresDevice
+import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.FlickerTestFactory
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt
index 240e90b9f019..6c833c4a5b62 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt
@@ -18,7 +18,7 @@ package com.android.server.wm.flicker.launch
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
-import android.platform.test.annotations.RequiresDevice
+import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.FlickerTestFactory
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
index 6388a5ae2259..d582931d882b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
@@ -19,9 +19,9 @@ package com.android.server.wm.flicker.launch
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
-import android.platform.test.annotations.RequiresDevice
import android.view.WindowInsets
import android.view.WindowManager
+import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.FlickerBuilder
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index 9106835fb7b4..db4baa039856 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -18,7 +18,7 @@ package com.android.server.wm.flicker.launch
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
-import android.platform.test.annotations.RequiresDevice
+import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.FlickerTestFactory
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
index 142c6888e776..7cfe87904dc3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
@@ -19,7 +19,7 @@ package com.android.server.wm.flicker.launch
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
-import android.platform.test.annotations.RequiresDevice
+import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
index 62d7cc099771..7a7990f4e36c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
@@ -18,7 +18,7 @@ package com.android.server.wm.flicker.launch
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
-import android.platform.test.annotations.RequiresDevice
+import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.FlickerTestFactory
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
index b064695554bb..31babb8479b6 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
@@ -18,6 +18,7 @@ package com.android.server.wm.flicker.launch
import android.app.Instrumentation
import android.app.WallpaperManager
+import android.content.res.Resources
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
@@ -29,8 +30,8 @@ import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.NewTasksAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.DEFAULT_TASK_DISPLAY_AREA
import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.SPLASH_SCREEN
@@ -64,9 +65,7 @@ import org.junit.runners.Parameterized
class TaskTransitionTest(flicker: FlickerTest) : BaseTest(flicker) {
private val launchNewTaskApp = NewTasksAppHelper(instrumentation)
private val simpleApp = SimpleAppHelper(instrumentation)
- private val wallpaper by lazy {
- getWallpaperPackage(instrumentation) ?: error("Unable to obtain wallpaper")
- }
+ private val wallpaper by lazy { getWallpaperPackage(instrumentation) }
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
@@ -143,8 +142,7 @@ class TaskTransitionTest(flicker: FlickerTest) : BaseTest(flicker) {
val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
flicker.assertLayers {
- this
- .invoke("LAUNCH_NEW_TASK_ACTIVITY coversExactly displayBounds") {
+ this.invoke("LAUNCH_NEW_TASK_ACTIVITY coversExactly displayBounds") {
it.visibleRegion(launchNewTaskApp.componentMatcher).coversExactly(displayBounds)
}
.isInvisible(backgroundColorLayer)
@@ -159,7 +157,7 @@ class TaskTransitionTest(flicker: FlickerTest) : BaseTest(flicker) {
"SIMPLE_ACTIVITY's splashscreen coversExactly displayBounds",
isOptional = true
) {
- it.visibleRegion(ComponentSplashScreenMatcher( simpleApp.componentMatcher))
+ it.visibleRegion(ComponentSplashScreenMatcher(simpleApp.componentMatcher))
.coversExactly(displayBounds)
}
.invoke("SIMPLE_ACTIVITY coversExactly displayBounds") {
@@ -178,7 +176,8 @@ class TaskTransitionTest(flicker: FlickerTest) : BaseTest(flicker) {
isOptional = true
) {
it.visibleRegion(
- ComponentSplashScreenMatcher(launchNewTaskApp.componentMatcher))
+ ComponentSplashScreenMatcher(launchNewTaskApp.componentMatcher)
+ )
.coversExactly(displayBounds)
}
.invoke("LAUNCH_NEW_TASK_ACTIVITY coversExactly displayBounds") {
@@ -215,10 +214,20 @@ class TaskTransitionTest(flicker: FlickerTest) : BaseTest(flicker) {
override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
companion object {
- private fun getWallpaperPackage(instrumentation: Instrumentation): IComponentMatcher? {
+ private fun getWallpaperPackage(instrumentation: Instrumentation): IComponentMatcher {
val wallpaperManager = WallpaperManager.getInstance(instrumentation.targetContext)
return wallpaperManager.wallpaperInfo?.component?.toFlickerComponent()
+ ?: getStaticWallpaperPackage(instrumentation)
+ }
+
+ private fun getStaticWallpaperPackage(instrumentation: Instrumentation): IComponentMatcher {
+ val resourceId =
+ Resources.getSystem()
+ .getIdentifier("image_wallpaper_component", "string", "android")
+ return ComponentNameMatcher.unflattenFromString(
+ instrumentation.targetContext.resources.getString(resourceId)
+ )
}
@Parameterized.Parameters(name = "{0}")
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
index b4a67eff75ee..be3b0bf15401 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
@@ -17,7 +17,7 @@
package com.android.server.wm.flicker.quickswitch
import android.platform.test.annotations.Presubmit
-import android.platform.test.annotations.RequiresDevice
+import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
index 6dc11b5de1f2..25d9753b363f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
@@ -17,7 +17,7 @@
package com.android.server.wm.flicker.quickswitch
import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.RequiresDevice
+import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
index 593481cfc221..18d1d3ce701e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
@@ -17,7 +17,7 @@
package com.android.server.wm.flicker.quickswitch
import android.platform.test.annotations.Presubmit
-import android.platform.test.annotations.RequiresDevice
+import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
index 5a78868a8a05..b40ecac2d19a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
@@ -17,7 +17,7 @@
package com.android.server.wm.flicker.quickswitch
import android.platform.test.annotations.FlakyTest
-import android.platform.test.annotations.RequiresDevice
+import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
index 456eab141f93..df91754765ba 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
@@ -18,7 +18,7 @@ package com.android.server.wm.flicker.quickswitch
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
-import android.platform.test.annotations.RequiresDevice
+import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index 741ae51887a3..23edfb662693 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -19,8 +19,8 @@ package com.android.server.wm.flicker.rotation
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
-import android.platform.test.annotations.RequiresDevice
import android.view.WindowManager
+import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.FlickerTestFactory
@@ -253,7 +253,7 @@ open class SeamlessAppRotationTest(flicker: FlickerTest) : RotationTransition(fl
* from [FlickerTestFactory.rotationTests], but adding a flag (
* [ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD]) to indicate if the app should
* starve the UI thread of not
- */
+ */
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTest> {
diff --git a/tests/FrameworkPerf/AndroidManifest.xml b/tests/FrameworkPerf/AndroidManifest.xml
index 07e775aeb838..9696fc31469a 100644
--- a/tests/FrameworkPerf/AndroidManifest.xml
+++ b/tests/FrameworkPerf/AndroidManifest.xml
@@ -3,6 +3,16 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.frameworkperf">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/>
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL"/>
+ <uses-permission android:name="android.permission.MANAGE_OWN_CALLS"/>
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION"/>
+ <uses-permission android:name="Manifest.permission.ACCESS_FINE_LOCATION"/>
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE"/>
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
+
+
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-sdk android:minSdkVersion="5"/>
diff --git a/tests/Input/src/com/android/test/input/InputDeviceTest.java b/tests/Input/src/com/android/test/input/InputDeviceTest.java
index 4da530442c49..797e818285f9 100644
--- a/tests/Input/src/com/android/test/input/InputDeviceTest.java
+++ b/tests/Input/src/com/android/test/input/InputDeviceTest.java
@@ -18,7 +18,6 @@ package android.view;
import static org.junit.Assert.assertEquals;
-import android.hardware.input.InputDeviceCountryCode;
import android.os.Parcel;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -55,7 +54,6 @@ public class InputDeviceTest {
assertEquals(device.isExternal(), outDevice.isExternal());
assertEquals(device.getSources(), outDevice.getSources());
assertEquals(device.getKeyboardType(), outDevice.getKeyboardType());
- assertEquals(device.getCountryCode(), outDevice.getCountryCode());
assertEquals(device.getKeyboardLanguageTag(), outDevice.getKeyboardLanguageTag());
assertEquals(device.getKeyboardLayoutType(), outDevice.getKeyboardLayoutType());
assertEquals(device.getMotionRanges().size(), outDevice.getMotionRanges().size());
@@ -88,7 +86,6 @@ public class InputDeviceTest {
.setHasButtonUnderPad(true)
.setHasSensor(true)
.setHasBattery(true)
- .setCountryCode(InputDeviceCountryCode.INTERNATIONAL)
.setKeyboardLanguageTag("en-US")
.setKeyboardLayoutType("qwerty")
.setSupportsUsi(true)
diff --git a/tests/OneMedia/AndroidManifest.xml b/tests/OneMedia/AndroidManifest.xml
index 7fc352405212..ddde6dbfefb0 100644
--- a/tests/OneMedia/AndroidManifest.xml
+++ b/tests/OneMedia/AndroidManifest.xml
@@ -7,6 +7,7 @@
<uses-sdk android:minSdkVersion="19"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
@@ -27,7 +28,8 @@
</activity>
<service android:name="com.android.onemedia.OnePlayerService"
android:exported="true"
- android:process="com.android.onemedia.service"/>
+ android:process="com.android.onemedia.service"
+ android:foregroundServiceType="mediaPlayback"/>
</application>
</manifest>
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeScreenRecordTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeScreenRecordTests.kt
index 996a1d3d79da..7378476554ec 100644
--- a/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeScreenRecordTests.kt
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeScreenRecordTests.kt
@@ -18,7 +18,6 @@ package com.android.test
import android.graphics.Color
import android.graphics.Rect
import android.os.SystemClock
-import android.view.cts.surfacevalidator.PixelColor
import junit.framework.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
@@ -53,7 +52,7 @@ class SharedBufferModeScreenRecordTests(useBlastAdapter: Boolean) :
SystemClock.sleep(4000)
}
- val result = withScreenRecording(svBounds, PixelColor.RED) {
+ val result = withScreenRecording(svBounds, Color.RED) {
it.mSurfaceProxy.drawBuffer(0, Color.RED)
}
val failRatio = 1.0f * result.failFrames / (result.failFrames + result.passFrames)
diff --git a/tests/VectorDrawableTest/OWNERS b/tests/VectorDrawableTest/OWNERS
new file mode 100644
index 000000000000..27e16681899e
--- /dev/null
+++ b/tests/VectorDrawableTest/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 24939
+
+include /graphics/java/android/graphics/OWNERS
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
index c1e47e906e22..268c565b65ff 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
@@ -25,12 +25,14 @@ import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.Scope
import com.android.tools.lint.detector.api.Severity
import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.google.android.lint.findCallExpression
import com.intellij.psi.PsiElement
import org.jetbrains.uast.UBlockExpression
import org.jetbrains.uast.UDeclarationsExpression
import org.jetbrains.uast.UElement
import org.jetbrains.uast.UExpression
import org.jetbrains.uast.UMethod
+import org.jetbrains.uast.skipParenthesizedExprDown
class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner {
override fun getApplicableUastTypes(): List<Class<out UElement?>> =
@@ -43,34 +45,35 @@ class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner {
if (context.evaluator.isAbstract(node)) return
if (!node.hasAnnotation(ANNOTATION_ENFORCE_PERMISSION)) return
- val targetExpression = "super.${node.name}$HELPER_SUFFIX()"
+ val targetExpression = "${node.name}$HELPER_SUFFIX()"
+ val message = "Method must start with $targetExpression or super.${node.name}()"
- val body = node.uastBody as? UBlockExpression
- if (body == null) {
- context.report(
- ISSUE_ENFORCE_PERMISSION_HELPER,
- context.getLocation(node),
- "Method must start with $targetExpression",
- )
- return
- }
+ val firstExpression = (node.uastBody as? UBlockExpression)
+ ?.expressions?.firstOrNull()
- val firstExpression = body.expressions.firstOrNull()
if (firstExpression == null) {
context.report(
ISSUE_ENFORCE_PERMISSION_HELPER,
context.getLocation(node),
- "Method must start with $targetExpression",
+ message,
)
return
}
- val firstExpressionSource = firstExpression.asSourceString()
- .filterNot(Char::isWhitespace)
+ val firstExpressionSource = firstExpression.skipParenthesizedExprDown()
+ .asSourceString()
+ .filterNot(Char::isWhitespace)
+
+ if (firstExpressionSource != targetExpression &&
+ firstExpressionSource != "super.$targetExpression") {
+ // calling super.<methodName>() is also legal
+ val directSuper = context.evaluator.getSuperMethod(node)
+ val firstCall = findCallExpression(firstExpression)?.resolve()
+ if (directSuper != null && firstCall == directSuper) return
- if (firstExpressionSource != targetExpression) {
val locationTarget = getLocationTarget(firstExpression)
val expressionLocation = context.getLocation(locationTarget)
+
val indent = " ".repeat(expressionLocation.start?.column ?: 0)
val fix = fix()
@@ -85,7 +88,7 @@ class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner {
context.report(
ISSUE_ENFORCE_PERMISSION_HELPER,
context.getLocation(node),
- "Method must start with $targetExpression",
+ message,
fix
)
}
@@ -99,7 +102,8 @@ class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner {
When @EnforcePermission is applied, the AIDL compiler generates a Stub method to do the
permission check called yourMethodName$HELPER_SUFFIX.
- You must call this method as the first expression in your implementation.
+ yourMethodName$HELPER_SUFFIX must be executed before any other operation. To do that, you can
+ either call it directly or indirectly via super.yourMethodName().
"""
val ISSUE_ENFORCE_PERMISSION_HELPER: Issue = Issue.create(
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt
index 4799184b0e23..df7ebd7e1e7a 100644
--- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt
@@ -47,7 +47,7 @@ class EnforcePermissionHelperDetectorTest : LintDetectorTest() {
.run()
.expect(
"""
- src/Foo.java:5: Error: Method must start with super.test_enforcePermission() [MissingEnforcePermissionHelper]
+ src/Foo.java:5: Error: Method must start with test_enforcePermission() or super.test() [MissingEnforcePermissionHelper]
@Override
^
1 errors, 0 warnings
@@ -85,7 +85,7 @@ class EnforcePermissionHelperDetectorTest : LintDetectorTest() {
.run()
.expect(
"""
- src/Foo.java:5: Error: Method must start with super.test_enforcePermission() [MissingEnforcePermissionHelper]
+ src/Foo.java:5: Error: Method must start with test_enforcePermission() or super.test() [MissingEnforcePermissionHelper]
@Override
^
1 errors, 0 warnings
@@ -120,7 +120,7 @@ class EnforcePermissionHelperDetectorTest : LintDetectorTest() {
.run()
.expect(
"""
- src/Foo.java:5: Error: Method must start with super.test_enforcePermission() [MissingEnforcePermissionHelper]
+ src/Foo.java:5: Error: Method must start with test_enforcePermission() or super.test() [MissingEnforcePermissionHelper]
@Override
^
1 errors, 0 warnings
@@ -150,6 +150,28 @@ class EnforcePermissionHelperDetectorTest : LintDetectorTest() {
.expectClean()
}
+ fun testHelperWithoutSuperPrefix_Okay() {
+ lint().files(
+ java(
+ """
+ import android.content.Context;
+ import android.test.ITest;
+ public class Foo extends ITest.Stub {
+ private Context mContext;
+ @Override
+ @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+ public void test() throws android.os.RemoteException {
+ test_enforcePermission();
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expectClean()
+ }
+
fun testInterfaceDefaultMethod_wouldStillReport() {
lint().files(
java(
@@ -167,7 +189,7 @@ class EnforcePermissionHelperDetectorTest : LintDetectorTest() {
.run()
.expect(
"""
- src/IProtected.java:2: Error: Method must start with super.PermissionProtected_enforcePermission() [MissingEnforcePermissionHelper]
+ src/IProtected.java:2: Error: Method must start with super.PermissionProtected_enforcePermission() or super.PermissionProtected() [MissingEnforcePermissionHelper]
@android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
^
1 errors, 0 warnings
@@ -175,6 +197,216 @@ class EnforcePermissionHelperDetectorTest : LintDetectorTest() {
)
}
+ fun testInheritance_callSuper_okay() {
+ lint().files(
+ java(
+ """
+ package test;
+ import android.content.Context;
+ import android.test.ITest;
+ public class Foo extends ITest.Stub {
+ private Context mContext;
+ @Override
+ @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+ public void test() throws android.os.RemoteException {
+ super.test_enforcePermission();
+ }
+ }
+ """
+ ).indented(),
+ java(
+ """
+ package test;
+ import test.Foo;
+ public class Bar extends Foo {
+ @Override
+ @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+ public void test() throws android.os.RemoteException {
+ super.test();
+ }
+ }
+ """
+ ).indented(),
+ java(
+ """
+ package test;
+ import test.Bar;
+ public class Baz extends Bar {
+ @Override
+ @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+ public void test() throws android.os.RemoteException {
+ super.test();
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expectClean()
+ }
+
+ fun testInheritance_callHelper_okay() {
+ lint().files(
+ java(
+ """
+ package test;
+ import android.content.Context;
+ import android.test.ITest;
+ public class Foo extends ITest.Stub {
+ private Context mContext;
+ @Override
+ @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+ public void test() throws android.os.RemoteException {
+ super.test_enforcePermission();
+ }
+ }
+ """
+ ).indented(),
+ java(
+ """
+ package test;
+ import test.Foo;
+ public class Bar extends Foo {
+ @Override
+ @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+ public void test() throws android.os.RemoteException {
+ super.test();
+ }
+ }
+ """
+ ).indented(),
+ java(
+ """
+ package test;
+ import test.Bar;
+ public class Baz extends Bar {
+ @Override
+ @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+ public void test() throws android.os.RemoteException {
+ super.test_enforcePermission();
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expectClean()
+ }
+
+ fun testInheritance_missingCallInChain_error() {
+ lint().files(
+ java(
+ """
+ package test;
+ import android.content.Context;
+ import android.test.ITest;
+ public class Foo extends ITest.Stub {
+ private Context mContext;
+ @Override
+ @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+ public void test() throws android.os.RemoteException {
+ super.test_enforcePermission();
+ }
+ }
+ """
+ ).indented(),
+ java(
+ """
+ package test;
+ import test.Foo;
+ public class Bar extends Foo {
+ @Override
+ @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+ public void test() throws android.os.RemoteException {
+ doStuff();
+ }
+ }
+ """
+ ).indented(),
+ java(
+ """
+ package test;
+ import test.Bar;
+ public class Baz extends Bar {
+ @Override
+ @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+ public void test() throws android.os.RemoteException {
+ super.test();
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/test/Bar.java:4: Error: Method must start with test_enforcePermission() or super.test() [MissingEnforcePermissionHelper]
+ @Override
+ ^
+ 1 errors, 0 warnings
+ """
+ )
+ }
+
+ fun testInheritance_missingCall_error() {
+ lint().files(
+ java(
+ """
+ package test;
+ import android.content.Context;
+ import android.test.ITest;
+ public class Foo extends ITest.Stub {
+ private Context mContext;
+ @Override
+ @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+ public void test() throws android.os.RemoteException {
+ super.test_enforcePermission();
+ }
+ }
+ """
+ ).indented(),
+ java(
+ """
+ package test;
+ import test.Foo;
+ public class Bar extends Foo {
+ @Override
+ @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+ public void test() throws android.os.RemoteException {
+ super.test();
+ }
+ }
+ """
+ ).indented(),
+ java(
+ """
+ package test;
+ import test.Bar;
+ public class Baz extends Bar {
+ @Override
+ @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+ public void test() throws android.os.RemoteException {
+ doStuff();
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/test/Baz.java:4: Error: Method must start with test_enforcePermission() or super.test() [MissingEnforcePermissionHelper]
+ @Override
+ ^
+ 1 errors, 0 warnings
+ """
+ )
+ }
+
companion object {
val stubs = arrayOf(aidlStub, contextStub, binderStub)
}
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt
index 362ac61ff6e8..f6e58da78e66 100644
--- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt
@@ -7,7 +7,9 @@ val aidlStub: TestFile = java(
"""
package android.test;
public interface ITest extends android.os.IInterface {
- public static abstract class Stub extends android.os.Binder implements android.test.ITest {}
+ public static abstract class Stub extends android.os.Binder implements android.test.ITest {
+ protected void test_enforcePermission() throws SecurityException {}
+ }
public void test() throws android.os.RemoteException;
}
"""