summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp28
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java14
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java14
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java10
-rw-r--r--core/api/current.txt22
-rw-r--r--core/api/system-current.txt11
-rw-r--r--core/api/test-current.txt5
-rw-r--r--core/java/android/app/ActivityThread.java11
-rw-r--r--core/java/android/app/INotificationManager.aidl2
-rw-r--r--core/java/android/app/NotificationManager.java13
-rw-r--r--core/java/android/app/WindowConfiguration.java7
-rw-r--r--core/java/android/app/servertransaction/ClientTransactionListenerController.java10
-rw-r--r--core/java/android/app/servertransaction/TransactionExecutor.java8
-rw-r--r--core/java/android/companion/virtual/camera/VirtualCameraConfig.java40
-rw-r--r--core/java/android/content/pm/PackageManager.java96
-rw-r--r--core/java/android/content/pm/flags.aconfig8
-rw-r--r--core/java/android/content/pm/multiuser.aconfig7
-rw-r--r--core/java/android/hardware/SystemSensorManager.java9
-rw-r--r--core/java/android/hardware/camera2/CameraCharacteristics.java6
-rw-r--r--core/java/android/hardware/display/DisplayManagerInternal.java17
-rw-r--r--core/java/android/nfc/INfcAdapter.aidl9
-rw-r--r--core/java/android/nfc/INfcWlcStateListener.aidl (renamed from packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallReady.java)25
-rw-r--r--core/java/android/nfc/NfcAdapter.java164
-rw-r--r--core/java/android/nfc/NfcWlcStateListener.java120
-rw-r--r--core/java/android/nfc/WlcLDeviceInfo.aidl (renamed from packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStaging.java)12
-rw-r--r--core/java/android/nfc/WlcLDeviceInfo.java107
-rw-r--r--core/java/android/nfc/cardemulation/ApduServiceInfo.java4
-rw-r--r--core/java/android/nfc/flags.aconfig14
-rw-r--r--core/java/android/os/Process.java3
-rw-r--r--core/java/android/os/ServiceSpecificException.java2
-rw-r--r--core/java/android/provider/Settings.java27
-rw-r--r--core/java/android/security/flags.aconfig8
-rw-r--r--core/java/android/view/InsetsSource.java4
-rw-r--r--core/java/android/view/InsetsSourceConsumer.java4
-rw-r--r--core/java/android/view/InsetsSourceControl.java6
-rw-r--r--core/java/android/view/WindowInsets.java5
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java9
-rw-r--r--core/java/android/webkit/URLUtil.java97
-rw-r--r--core/java/android/webkit/WebSettings.java8
-rw-r--r--core/java/android/window/flags/windowing_sdk.aconfig14
-rw-r--r--core/java/com/android/internal/app/ResolverActivity.java51
-rw-r--r--core/java/com/android/internal/content/PackageMonitor.java29
-rw-r--r--core/java/com/android/internal/pm/parsing/PackageInfoCommonUtils.java652
-rw-r--r--core/java/com/android/internal/pm/parsing/PackageParser2.java19
-rw-r--r--core/jni/android_hardware_SensorManager.cpp16
-rw-r--r--core/proto/android/providers/settings/global.proto5
-rw-r--r--core/proto/android/view/insetssource.proto2
-rw-r--r--core/proto/android/view/insetssourceconsumer.proto3
-rw-r--r--core/proto/android/view/insetssourcecontrol.proto3
-rw-r--r--core/res/res/values/attrs_manifest.xml7
-rw-r--r--core/res/res/values/config.xml3
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java2
-rw-r--r--core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt18
-rw-r--r--core/tests/coretests/src/android/window/flags/WindowFlagsTest.java7
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java109
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java11
-rw-r--r--core/tests/overlaytests/Android.mk15
-rw-r--r--core/tests/overlaytests/host/Android.mk19
-rw-r--r--core/tests/overlaytests/host/test-apps/Android.mk16
-rw-r--r--core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.bp57
-rw-r--r--core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk56
-rw-r--r--core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.bp97
-rw-r--r--core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk93
-rw-r--r--data/etc/platform.xml6
-rw-r--r--data/etc/privapp-permissions-platform.xml1
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java4
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml6
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java37
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java65
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java60
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt20
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt39
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt39
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java5
-rw-r--r--location/java/android/location/LocationResult.java78
-rw-r--r--location/java/android/location/flags/gnss.aconfig7
-rw-r--r--media/java/android/media/flags/media_better_together.aconfig6
-rw-r--r--media/java/android/media/session/PlaybackState.java22
-rw-r--r--media/jni/android_media_tv_Tuner.cpp66
-rw-r--r--media/jni/android_media_tv_Tuner.h1
-rw-r--r--native/android/performance_hint.cpp74
-rw-r--r--packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml87
-rw-r--r--packages/LocalTransport/src/com/android/localtransport/LocalTransport.java3
-rw-r--r--packages/PackageInstaller/Android.bp15
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.java912
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt867
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt134
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.java462
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt440
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/model/SessionStager.java126
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/model/SessionStager.kt110
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.java716
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt739
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallStages.kt112
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallAborted.java127
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallFailed.java69
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallInstalling.java47
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStage.java34
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallSuccess.java95
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallUserActionRequired.java99
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallAborted.java71
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallFailed.java119
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallReady.java27
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallSuccess.java79
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUninstalling.java43
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUserActionRequired.java74
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.kt (renamed from packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.java)23
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.java354
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt348
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.kt (renamed from packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.java)10
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.java167
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.kt169
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/AnonymousSourceFragment.java4
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java2
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java2
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallFailedFragment.java2
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallInstallingFragment.java2
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallSuccessFragment.java4
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/SimpleErrorFragment.java2
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallConfirmationFragment.java2
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallErrorFragment.java2
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallUninstallingFragment.java2
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.java105
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.kt96
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModelFactory.java45
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModelFactory.kt33
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.java69
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.kt62
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModelFactory.java46
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModelFactory.kt34
-rw-r--r--packages/SettingsLib/AdaptiveIcon/Android.bp7
-rw-r--r--packages/SettingsLib/Android.bp3
-rw-r--r--packages/SettingsLib/EmergencyNumber/Android.bp3
-rw-r--r--packages/SettingsLib/MainSwitchPreference/Android.bp3
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/Android.bp3
-rw-r--r--packages/SettingsLib/SchedulesProvider/Android.bp5
-rw-r--r--packages/SettingsLib/SearchProvider/Android.bp3
-rw-r--r--packages/SettingsLib/Tile/Android.bp5
-rw-r--r--packages/SettingsLib/Utils/Android.bp3
-rw-r--r--packages/SettingsLib/search/Android.bp6
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java2
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java3
-rw-r--r--packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java1
-rw-r--r--packages/SystemUI/aconfig/Android.bp3
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig7
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt36
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt51
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt274
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt20
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/OffsetOperators.kt (renamed from packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallStage.java)22
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt71
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt125
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/ViewBasedLockscreenContent.kt111
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt25
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt190
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt200
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt25
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt114
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt35
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SettingsMenuSection.kt101
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt2
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt19
-rw-r--r--packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt6
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml2
-rw-r--r--packages/SystemUI/res/drawable/connected_display_dialog_bg.xml23
-rw-r--r--packages/SystemUI/res/layout/connected_display_dialog.xml7
-rw-r--r--packages/SystemUI/res/values/ids.xml2
-rw-r--r--packages/SystemUI/res/values/strings.xml2
-rw-r--r--packages/SystemUI/shared/Android.bp12
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/CoreStartable.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/ScreenDecorations.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/ScreenDecorationsModule.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIApplication.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/SystemActionsModule.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java (renamed from packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java)8
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt92
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt67
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt57
-rw-r--r--packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/haptics/slider/SliderState.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt111
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/PowerUI.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayModule.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/Recents.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsModule.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt65
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerStartable.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java184
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.kt157
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/toast/ToastModule.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/toast/ToastUI.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java)13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt77
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt188
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt30
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt30
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java86
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java61
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt19
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt161
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt10
-rw-r--r--packages/SystemUI/unfold/Android.bp3
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt48
-rw-r--r--ravenwood/ravenwood-annotation-allowed-classes.txt1
-rw-r--r--services/backup/Android.bp8
-rw-r--r--services/companion/Android.bp3
-rw-r--r--services/companion/java/com/android/server/companion/PackageUtils.java7
-rw-r--r--services/core/Android.bp6
-rw-r--r--services/core/java/android/content/pm/PackageManagerInternal.java5
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java38
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerConstants.java41
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java9
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerShellCommand.java8
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerUtils.java8
-rw-r--r--services/core/java/com/android/server/am/AppProfiler.java7
-rw-r--r--services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java2
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java8
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java4
-rw-r--r--services/core/java/com/android/server/am/ProcessProfileRecord.java3
-rw-r--r--services/core/java/com/android/server/am/ProcessStateRecord.java3
-rw-r--r--services/core/java/com/android/server/am/SettingsToPropertiesMapper.java2
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java20
-rw-r--r--services/core/java/com/android/server/audio/FadeOutManager.java1
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/FaceService.java58
-rw-r--r--services/core/java/com/android/server/display/AutomaticBrightnessController.java1
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java6
-rw-r--r--services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java18
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java14
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController2.java18
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerControllerInterface.java17
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerState.java15
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java8
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java8
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodUtils.java52
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssLocationProvider.java13
-rw-r--r--services/core/java/com/android/server/location/provider/LocationProviderManager.java28
-rw-r--r--services/core/java/com/android/server/location/provider/MockLocationProvider.java7
-rw-r--r--services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java5
-rw-r--r--services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java12
-rw-r--r--services/core/java/com/android/server/media/MediaSession2Record.java10
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecord.java32
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecordImpl.java11
-rw-r--r--services/core/java/com/android/server/media/MediaSessionService.java39
-rw-r--r--services/core/java/com/android/server/net/NetworkPolicyManagerService.java13
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java55
-rw-r--r--services/core/java/com/android/server/pm/BroadcastHelper.java16
-rw-r--r--services/core/java/com/android/server/pm/DeletePackageHelper.java3
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java15
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java1
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommand.java8
-rw-r--r--services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java38
-rw-r--r--services/core/java/com/android/server/pm/Settings.java9
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerService.java57
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java4
-rw-r--r--services/core/java/com/android/server/policy/WindowManagerPolicy.java6
-rw-r--r--services/core/java/com/android/server/power/hint/HintManagerService.java16
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java70
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java7
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java35
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java10
-rw-r--r--services/core/java/com/android/server/wm/AsyncRotationController.java28
-rw-r--r--services/core/java/com/android/server/wm/BackNavigationController.java2
-rw-r--r--services/core/java/com/android/server/wm/ClientLifecycleManager.java6
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java27
-rw-r--r--services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java41
-rw-r--r--services/core/java/com/android/server/wm/InputManagerCallback.java6
-rw-r--r--services/core/java/com/android/server/wm/KeyguardController.java5
-rw-r--r--services/core/java/com/android/server/wm/LetterboxUiController.java2
-rw-r--r--services/core/java/com/android/server/wm/RecentsAnimation.java11
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java36
-rw-r--r--services/core/java/com/android/server/wm/Session.java4
-rw-r--r--services/core/java/com/android/server/wm/Task.java43
-rw-r--r--services/core/java/com/android/server/wm/TaskDisplayArea.java6
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java16
-rw-r--r--services/core/java/com/android/server/wm/Transition.java13
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerFlags.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java33
-rw-r--r--services/core/java/com/android/server/wm/WindowProcessController.java17
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java9
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp11
-rw-r--r--services/java/com/android/server/SystemServer.java6
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayPowerStateTest.kt10
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java10
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java25
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java52
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java72
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java21
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java16
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java25
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java145
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java20
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java19
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java12
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java9
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskTests.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TransitionTests.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java13
-rw-r--r--services/usb/Android.bp3
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java14
-rw-r--r--tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt4
407 files changed, 9791 insertions, 6112 deletions
diff --git a/Android.bp b/Android.bp
index bb9304819e80..13b170353dd6 100644
--- a/Android.bp
+++ b/Android.bp
@@ -174,6 +174,9 @@ java_library {
// and remove this line.
"//frameworks/base/tools/hoststubgen:__subpackages__",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
// AIDL files under these paths are mixture of public and private ones.
@@ -264,6 +267,9 @@ java_library {
],
sdk_version: "core_platform",
installable: false,
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
// NOTE: This filegroup is exposed for vendor libraries to depend on and is referenced in
@@ -432,6 +438,9 @@ java_library {
],
sdk_version: "core_platform",
installable: false,
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
// Separated so framework-minus-apex-defaults can be used without the libs dependency
@@ -475,6 +484,9 @@ java_library {
],
compile_dex: false,
headers_only: true,
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
java_library {
@@ -502,6 +514,9 @@ java_library {
"-Xep:AndroidFrameworkUid:ERROR",
],
},
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
java_library {
@@ -516,6 +531,7 @@ java_library {
},
lint: {
enabled: false,
+ baseline_filename: "lint-baseline.xml",
},
}
@@ -540,6 +556,9 @@ java_library {
],
sdk_version: "core_platform",
apex_available: ["//apex_available:platform"],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
java_library {
@@ -555,6 +574,9 @@ java_library {
"calendar-provider-compat-config",
"contacts-provider-platform-compat-config",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
platform_compat_config {
@@ -609,6 +631,9 @@ java_library {
"rappor",
],
dxflags: ["--core-library"],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
// utility classes statically linked into framework-wifi and dynamically linked
@@ -634,6 +659,9 @@ java_library {
"//frameworks/base/services/net",
"//packages/modules/Wifi/framework",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
filegroup {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 900c90203f41..d940e380fa7e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1830,7 +1830,12 @@ public class JobSchedulerService extends com.android.server.SystemService
/* system_measured_calling_download_bytes */0,
/* system_measured_calling_upload_bytes */ 0,
jobStatus.getJob().getIntervalMillis(),
- jobStatus.getJob().getFlexMillis());
+ jobStatus.getJob().getFlexMillis(),
+ jobStatus.hasFlexibilityConstraint(),
+ /* isFlexConstraintSatisfied */ false,
+ jobStatus.canApplyTransportAffinities(),
+ jobStatus.getNumAppliedFlexibleConstraints(),
+ jobStatus.getNumDroppedFlexibleConstraints());
// If the job is immediately ready to run, then we can just immediately
// put it in the pending list and try to schedule it. This is especially
@@ -2273,7 +2278,12 @@ public class JobSchedulerService extends com.android.server.SystemService
/* system_measured_calling_download_bytes */0,
/* system_measured_calling_upload_bytes */ 0,
cancelled.getJob().getIntervalMillis(),
- cancelled.getJob().getFlexMillis());
+ cancelled.getJob().getFlexMillis(),
+ cancelled.hasFlexibilityConstraint(),
+ cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_FLEXIBLE),
+ cancelled.canApplyTransportAffinities(),
+ cancelled.getNumAppliedFlexibleConstraints(),
+ cancelled.getNumDroppedFlexibleConstraints());
}
// If this is a replacement, bring in the new version of the job
if (incomingJob != null) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 3addf9f98db2..fe55e2702916 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -536,7 +536,12 @@ public final class JobServiceContext implements ServiceConnection {
/* system_measured_calling_download_bytes */ 0,
/* system_measured_calling_upload_bytes */ 0,
job.getJob().getIntervalMillis(),
- job.getJob().getFlexMillis());
+ job.getJob().getFlexMillis(),
+ job.hasFlexibilityConstraint(),
+ job.isConstraintSatisfied(JobStatus.CONSTRAINT_FLEXIBLE),
+ job.canApplyTransportAffinities(),
+ job.getNumAppliedFlexibleConstraints(),
+ job.getNumDroppedFlexibleConstraints());
sEnqueuedJwiAtJobStart.logSampleWithUid(job.getUid(), job.getWorkCount());
final String sourcePackage = job.getSourcePackageName();
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
@@ -1620,7 +1625,12 @@ public final class JobServiceContext implements ServiceConnection {
TrafficStats.getUidTxBytes(completedJob.getUid())
- mInitialUploadedBytesFromCalling,
completedJob.getJob().getIntervalMillis(),
- completedJob.getJob().getFlexMillis());
+ completedJob.getJob().getFlexMillis(),
+ completedJob.hasFlexibilityConstraint(),
+ completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_FLEXIBLE),
+ completedJob.canApplyTransportAffinities(),
+ completedJob.getNumAppliedFlexibleConstraints(),
+ completedJob.getNumDroppedFlexibleConstraints());
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
getId());
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 0d5d11e98860..bdc2246475d6 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -106,11 +106,8 @@ public final class JobStatus {
public static final long NO_LATEST_RUNTIME = Long.MAX_VALUE;
public static final long NO_EARLIEST_RUNTIME = 0L;
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public static final int CONSTRAINT_CHARGING = JobInfo.CONSTRAINT_FLAG_CHARGING; // 1 < 0
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE; // 1 << 2
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public static final int CONSTRAINT_BATTERY_NOT_LOW =
JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW; // 1 << 1
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
@@ -125,7 +122,7 @@ public final class JobStatus {
static final int CONSTRAINT_WITHIN_QUOTA = 1 << 24; // Implicit constraint
static final int CONSTRAINT_PREFETCH = 1 << 23;
static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1 << 22; // Implicit constraint
- static final int CONSTRAINT_FLEXIBLE = 1 << 21; // Implicit constraint
+ public static final int CONSTRAINT_FLEXIBLE = 1 << 21; // Implicit constraint
private static final int IMPLICIT_CONSTRAINTS = 0
| CONSTRAINT_BACKGROUND_NOT_RESTRICTED
@@ -1534,7 +1531,7 @@ public final class JobStatus {
/**
* Returns the number of required flexible job constraints that have been dropped with time.
- * The lower this number is the easier it is for the flexibility constraint to be satisfied.
+ * The higher this number is the easier it is for the flexibility constraint to be satisfied.
*/
public int getNumDroppedFlexibleConstraints() {
return mNumDroppedFlexibleConstraints;
@@ -1600,7 +1597,8 @@ public final class JobStatus {
mTransportAffinitiesSatisfied = isSatisfied;
}
- boolean canApplyTransportAffinities() {
+ /** Whether transport affinities can be applied to the job in flex scheduling. */
+ public boolean canApplyTransportAffinities() {
return mCanApplyTransportAffinities;
}
diff --git a/core/api/current.txt b/core/api/current.txt
index a85a42169dcb..df073dda254a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -12932,6 +12932,7 @@ package android.content.pm {
field public static final String FEATURE_MIDI = "android.software.midi";
field public static final String FEATURE_NFC = "android.hardware.nfc";
field public static final String FEATURE_NFC_BEAM = "android.sofware.nfc.beam";
+ field @FlaggedApi("android.nfc.enable_nfc_charging") public static final String FEATURE_NFC_CHARGING = "android.hardware.nfc.charging";
field public static final String FEATURE_NFC_HOST_CARD_EMULATION = "android.hardware.nfc.hce";
field public static final String FEATURE_NFC_HOST_CARD_EMULATION_NFCF = "android.hardware.nfc.hcef";
field public static final String FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE = "android.hardware.nfc.ese";
@@ -28813,6 +28814,7 @@ package android.nfc {
method public void enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle);
method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context);
method @Nullable public android.nfc.NfcAntennaInfo getNfcAntennaInfo();
+ method @FlaggedApi("android.nfc.enable_nfc_charging") @Nullable public android.nfc.WlcLDeviceInfo getWlcLDeviceInfo();
method public boolean ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler);
method public boolean isEnabled();
method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean isObserveModeSupported();
@@ -28820,6 +28822,7 @@ package android.nfc {
method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionSupported();
method public boolean isSecureNfcEnabled();
method public boolean isSecureNfcSupported();
+ method @FlaggedApi("android.nfc.enable_nfc_charging") public boolean isWlcEnabled();
field public static final String ACTION_ADAPTER_STATE_CHANGED = "android.nfc.action.ADAPTER_STATE_CHANGED";
field public static final String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED";
field @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public static final String ACTION_PREFERRED_PAYMENT_CHANGED = "android.nfc.action.PREFERRED_PAYMENT_CHANGED";
@@ -28905,6 +28908,20 @@ package android.nfc {
ctor public TagLostException(String);
}
+ @FlaggedApi("android.nfc.enable_nfc_charging") public final class WlcLDeviceInfo implements android.os.Parcelable {
+ ctor public WlcLDeviceInfo(double, double, double, int);
+ method public int describeContents();
+ method public double getBatteryLevel();
+ method public double getProductId();
+ method public int getState();
+ method public double getTemperature();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int CONNECTED_CHARGING = 2; // 0x2
+ field public static final int CONNECTED_DISCHARGING = 3; // 0x3
+ field @NonNull public static final android.os.Parcelable.Creator<android.nfc.WlcLDeviceInfo> CREATOR;
+ field public static final int DISCONNECTED = 1; // 0x1
+ }
+
}
package android.nfc.cardemulation {
@@ -45682,6 +45699,7 @@ package android.telephony {
field public static final String ACTION_MULTI_SIM_CONFIG_CHANGED = "android.telephony.action.MULTI_SIM_CONFIG_CHANGED";
field public static final String ACTION_NETWORK_COUNTRY_CHANGED = "android.telephony.action.NETWORK_COUNTRY_CHANGED";
field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final String ACTION_PHONE_STATE_CHANGED = "android.intent.action.PHONE_STATE";
+ field @FlaggedApi("com.android.internal.telephony.flags.reset_mobile_network_settings") public static final String ACTION_RESET_MOBILE_NETWORK_SETTINGS = "android.telephony.action.RESET_MOBILE_NETWORK_SETTINGS";
field public static final String ACTION_RESPOND_VIA_MESSAGE = "android.intent.action.RESPOND_VIA_MESSAGE";
field public static final String ACTION_SECRET_CODE = "android.telephony.action.SECRET_CODE";
field public static final String ACTION_SHOW_VOICEMAIL_NOTIFICATION = "android.telephony.action.SHOW_VOICEMAIL_NOTIFICATION";
@@ -57411,7 +57429,7 @@ package android.webkit {
method public abstract boolean getBuiltInZoomControls();
method public abstract int getCacheMode();
method public abstract String getCursiveFontFamily();
- method public abstract boolean getDatabaseEnabled();
+ method @Deprecated public abstract boolean getDatabaseEnabled();
method @Deprecated public abstract String getDatabasePath();
method public abstract int getDefaultFixedFontSize();
method public abstract int getDefaultFontSize();
@@ -57457,7 +57475,7 @@ package android.webkit {
method public abstract void setBuiltInZoomControls(boolean);
method public abstract void setCacheMode(int);
method public abstract void setCursiveFontFamily(String);
- method public abstract void setDatabaseEnabled(boolean);
+ method @Deprecated public abstract void setDatabaseEnabled(boolean);
method @Deprecated public abstract void setDatabasePath(String);
method public abstract void setDefaultFixedFontSize(int);
method public abstract void setDefaultFontSize(int);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 51e61e6239bb..0d4169fb9121 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3357,7 +3357,7 @@ package android.companion.virtual.camera {
@FlaggedApi("android.companion.virtual.flags.virtual_camera") public final class VirtualCameraConfig implements android.os.Parcelable {
method public int describeContents();
- method @StringRes public int getDisplayNameStringRes();
+ method @NonNull public String getName();
method @NonNull public java.util.Set<android.companion.virtual.camera.VirtualCameraStreamConfig> getStreamConfigs();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.camera.VirtualCameraConfig> CREATOR;
@@ -3367,7 +3367,7 @@ package android.companion.virtual.camera {
ctor public VirtualCameraConfig.Builder();
method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder addStreamConfig(int, int, int);
method @NonNull public android.companion.virtual.camera.VirtualCameraConfig build();
- method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setDisplayNameStringRes(@StringRes int);
+ method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setName(@NonNull String);
method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setVirtualCameraCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.camera.VirtualCameraCallback);
}
@@ -9884,17 +9884,20 @@ package android.nfc {
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable();
method @FlaggedApi("android.nfc.enable_nfc_reader_option") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableReaderOption(boolean);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableSecureNfc(boolean);
+ method @FlaggedApi("android.nfc.enable_nfc_charging") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableWlc(boolean);
method @FlaggedApi("android.nfc.enable_nfc_mainline") public int getAdapterState();
method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public java.util.Map<java.lang.String,java.lang.Boolean> getTagIntentAppPreferenceForUser(int);
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOn();
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOnSupported();
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isTagIntentAppPreferenceSupported();
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void registerControllerAlwaysOnListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
+ method @FlaggedApi("android.nfc.enable_nfc_charging") public void registerWlcStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.WlcStateListener);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean removeNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler);
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean setControllerAlwaysOn(boolean);
method @FlaggedApi("android.nfc.enable_nfc_mainline") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setReaderMode(boolean);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int setTagIntentAppPreferenceForUser(int, @NonNull String, boolean);
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnListener(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
+ method @FlaggedApi("android.nfc.enable_nfc_charging") public void unregisterWlcStateListener(@NonNull android.nfc.NfcAdapter.WlcStateListener);
field @FlaggedApi("android.nfc.enable_nfc_mainline") public static final String ACTION_REQUIRE_UNLOCK_FOR_NFC = "android.nfc.action.REQUIRE_UNLOCK_FOR_NFC";
field public static final int TAG_INTENT_APP_PREF_RESULT_PACKAGE_NOT_FOUND = -1; // 0xffffffff
field public static final int TAG_INTENT_APP_PREF_RESULT_SUCCESS = 0; // 0x0
@@ -9909,6 +9912,10 @@ package android.nfc {
method public boolean onUnlockAttempted(android.nfc.Tag);
}
+ @FlaggedApi("android.nfc.enable_nfc_charging") public static interface NfcAdapter.WlcStateListener {
+ method public void onWlcStateChanged(@NonNull android.nfc.WlcLDeviceInfo);
+ }
+
}
package android.nfc.cardemulation {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index aaeba66e4a40..812ba6d5fde9 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -510,6 +510,7 @@ package android.app {
method public int getActivityType();
method @Nullable public android.graphics.Rect getAppBounds();
method @NonNull public android.graphics.Rect getBounds();
+ method public int getDisplayRotation();
method @NonNull public android.graphics.Rect getMaxBounds();
method public int getRotation();
method public int getWindowingMode();
@@ -3611,6 +3612,10 @@ package android.view {
method @Nullable public android.view.View getStatusBarBackgroundView();
}
+ public static final class WindowInsets.Type {
+ method @NonNull public static String toString(int);
+ }
+
public interface WindowManager extends android.view.ViewManager {
method public default int getDisplayImePolicy(int);
method public static boolean hasWindowExtensionsEnabled();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 6ddb36a72aa9..6df0f6b2ac8b 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -407,7 +407,7 @@ public final class ActivityThread extends ClientTransactionHandler
private int mLastSessionId;
// Holds the value of the last reported device ID value from the server for the top activity.
- int mLastReportedDeviceId;
+ int mLastReportedDeviceId = Context.DEVICE_ID_DEFAULT;
final ArrayMap<IBinder, CreateServiceData> mServicesData = new ArrayMap<>();
@UnsupportedAppUsage
final ArrayMap<IBinder, Service> mServices = new ArrayMap<>();
@@ -4856,10 +4856,13 @@ public final class ActivityThread extends ClientTransactionHandler
service.attach(context, this, data.info.name, data.token, app,
ActivityManager.getService());
if (!service.isUiContext()) { // WindowProviderService is a UI Context.
- VirtualDeviceManager vdm = context.getSystemService(VirtualDeviceManager.class);
- if (mLastReportedDeviceId == Context.DEVICE_ID_DEFAULT
- || vdm.isValidVirtualDeviceId(mLastReportedDeviceId)) {
+ if (mLastReportedDeviceId == Context.DEVICE_ID_DEFAULT) {
service.updateDeviceId(mLastReportedDeviceId);
+ } else {
+ VirtualDeviceManager vdm = context.getSystemService(VirtualDeviceManager.class);
+ if (vdm != null && vdm.isValidVirtualDeviceId(mLastReportedDeviceId)) {
+ service.updateDeviceId(mLastReportedDeviceId);
+ }
}
}
service.onCreate();
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index b7db5f5d8b0c..c3adbc30641f 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -222,7 +222,7 @@ interface INotificationManager
boolean removeAutomaticZenRule(String id, boolean fromUser);
boolean removeAutomaticZenRules(String packageName, boolean fromUser);
int getRuleInstanceCount(in ComponentName owner);
- void setAutomaticZenRuleState(String id, in Condition condition, boolean fromUser);
+ void setAutomaticZenRuleState(String id, in Condition condition);
byte[] getBackupPayload(int user);
void applyRestore(in byte[] payload, int user);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index f76a45b37661..0b6e24cf5545 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1391,20 +1391,9 @@ public class NotificationManager {
* @param condition The new state of this rule
*/
public void setAutomaticZenRuleState(@NonNull String id, @NonNull Condition condition) {
- if (Flags.modesApi()) {
- setAutomaticZenRuleState(id, condition,
- /* fromUser= */ condition.source == Condition.SOURCE_USER_ACTION);
- } else {
- setAutomaticZenRuleState(id, condition, /* fromUser= */ false);
- }
- }
-
- /** @hide */
- public void setAutomaticZenRuleState(@NonNull String id, @NonNull Condition condition,
- boolean fromUser) {
INotificationManager service = getService();
try {
- service.setAutomaticZenRuleState(id, condition, fromUser);
+ service.setAutomaticZenRuleState(id, condition);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index 46216343dcff..aa3b71a28eba 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -27,6 +27,7 @@ import static android.view.Surface.rotationToString;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.Configuration;
@@ -326,7 +327,7 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
}
/**
- * Sets the apparent display cutout.
+ * Sets the display rotation.
* @hide
*/
public void setDisplayRotation(@Surface.Rotation int rotation) {
@@ -386,9 +387,9 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
}
/**
- * @see #setDisplayRotation
- * @hide
+ * Gets the display rotation.
*/
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
public @Surface.Rotation int getDisplayRotation() {
return mDisplayRotation;
}
diff --git a/core/java/android/app/servertransaction/ClientTransactionListenerController.java b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
index 7418c06a2dd1..9f97f6ff7c39 100644
--- a/core/java/android/app/servertransaction/ClientTransactionListenerController.java
+++ b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
@@ -16,7 +16,7 @@
package android.app.servertransaction;
-import static com.android.window.flags.Flags.syncWindowConfigUpdateFlag;
+import static com.android.window.flags.Flags.bundleClientTransactionFlag;
import static java.util.Objects.requireNonNull;
@@ -67,7 +67,7 @@ public class ClientTransactionListenerController {
* window configuration.
*/
public void onDisplayChanged(int displayId) {
- if (!isSyncWindowConfigUpdateFlagEnabled()) {
+ if (!isBundleClientTransactionFlagEnabled()) {
return;
}
if (ActivityThread.isSystem()) {
@@ -77,9 +77,9 @@ public class ClientTransactionListenerController {
mDisplayManager.handleDisplayChangeFromWindowManager(displayId);
}
- /** Whether {@link #syncWindowConfigUpdateFlag} feature flag is enabled. */
- public boolean isSyncWindowConfigUpdateFlagEnabled() {
+ /** Whether {@link #bundleClientTransactionFlag} feature flag is enabled. */
+ public boolean isBundleClientTransactionFlagEnabled() {
// Can't read flag from isolated process.
- return !Process.isIsolated() && syncWindowConfigUpdateFlag();
+ return !Process.isIsolated() && bundleClientTransactionFlag();
}
}
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index 9f5e0dc14cca..ee48e431ba8e 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -32,7 +32,7 @@ import static android.app.servertransaction.TransactionExecutorHelper.shouldExcl
import static android.app.servertransaction.TransactionExecutorHelper.tId;
import static android.app.servertransaction.TransactionExecutorHelper.transactionToString;
-import static com.android.window.flags.Flags.syncWindowConfigUpdateFlag;
+import static com.android.window.flags.Flags.bundleClientTransactionFlag;
import android.annotation.NonNull;
import android.app.ActivityThread.ActivityClientRecord;
@@ -183,9 +183,9 @@ public class TransactionExecutor {
}
// Can't read flag from isolated process.
- final boolean isSyncWindowConfigUpdateFlagEnabled = !Process.isIsolated()
- && syncWindowConfigUpdateFlag();
- final Context configUpdatedContext = isSyncWindowConfigUpdateFlagEnabled
+ final boolean isBundleClientTransactionFlagEnabled = !Process.isIsolated()
+ && bundleClientTransactionFlag();
+ final Context configUpdatedContext = isBundleClientTransactionFlagEnabled
? item.getContextToUpdate(mTransactionHandler)
: null;
final Configuration preExecutedConfig = configUpdatedContext != null
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
index a9392518d0c6..59fe9a1c127b 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
+++ b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
@@ -20,11 +20,9 @@ import static java.util.Objects.requireNonNull;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
-import android.annotation.StringRes;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.companion.virtual.flags.Flags;
-import android.content.res.Resources;
import android.graphics.ImageFormat;
import android.os.Parcel;
import android.os.Parcelable;
@@ -45,16 +43,16 @@ import java.util.concurrent.Executor;
@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
public final class VirtualCameraConfig implements Parcelable {
- private final @StringRes int mNameStringRes;
+ private final String mName;
private final Set<VirtualCameraStreamConfig> mStreamConfigurations;
private final IVirtualCameraCallback mCallback;
private VirtualCameraConfig(
- int displayNameStringRes,
+ @NonNull String name,
@NonNull Set<VirtualCameraStreamConfig> streamConfigurations,
@NonNull Executor executor,
@NonNull VirtualCameraCallback callback) {
- mNameStringRes = displayNameStringRes;
+ mName = requireNonNull(name, "Missing name");
mStreamConfigurations =
Set.copyOf(requireNonNull(streamConfigurations, "Missing stream configurations"));
if (mStreamConfigurations.isEmpty()) {
@@ -68,7 +66,7 @@ public final class VirtualCameraConfig implements Parcelable {
}
private VirtualCameraConfig(@NonNull Parcel in) {
- mNameStringRes = in.readInt();
+ mName = in.readString8();
mCallback = IVirtualCameraCallback.Stub.asInterface(in.readStrongBinder());
mStreamConfigurations =
Set.of(
@@ -84,18 +82,18 @@ public final class VirtualCameraConfig implements Parcelable {
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeInt(mNameStringRes);
+ dest.writeString8(mName);
dest.writeStrongInterface(mCallback);
dest.writeParcelableArray(
mStreamConfigurations.toArray(new VirtualCameraStreamConfig[0]), flags);
}
/**
- * @return The display name of this VirtualCamera
+ * @return The name of this VirtualCamera
*/
- @StringRes
- public int getDisplayNameStringRes() {
- return mNameStringRes;
+ @NonNull
+ public String getName() {
+ return mName;
}
/**
@@ -126,30 +124,22 @@ public final class VirtualCameraConfig implements Parcelable {
* <li>At least one stream must be added with {@link #addStreamConfig(int, int, int)}.
* <li>A callback must be set with {@link #setVirtualCameraCallback(Executor,
* VirtualCameraCallback)}
- * <li>A user readable name can be set with {@link #setDisplayNameStringRes(int)}
+ * <li>A camera name must be set with {@link #setName(String)}
*/
@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
public static final class Builder {
- private @StringRes int mDisplayNameStringRes = Resources.ID_NULL;
+ private String mName;
private final ArraySet<VirtualCameraStreamConfig> mStreamConfigurations = new ArraySet<>();
private Executor mCallbackExecutor;
private VirtualCameraCallback mCallback;
/**
- * Set the visible name of this camera for the user.
- *
- * <p>Sets the resource to a string representing a user readable name for this virtual
- * camera.
- *
- * @throws IllegalArgumentException if an invalid resource id is passed.
+ * Set the name of the virtual camera instance.
*/
@NonNull
- public Builder setDisplayNameStringRes(@StringRes int displayNameStringRes) {
- if (displayNameStringRes <= 0) {
- throw new IllegalArgumentException("Invalid resource passed for display name");
- }
- mDisplayNameStringRes = displayNameStringRes;
+ public Builder setName(@NonNull String name) {
+ mName = requireNonNull(name, "Display name cannot be null");
return this;
}
@@ -203,7 +193,7 @@ public final class VirtualCameraConfig implements Parcelable {
@NonNull
public VirtualCameraConfig build() {
return new VirtualCameraConfig(
- mDisplayNameStringRes, mStreamConfigurations, mCallbackExecutor, mCallback);
+ mName, mStreamConfigurations, mCallbackExecutor, mCallback);
}
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index a8638708824b..8151a9133f5b 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -16,6 +16,8 @@
package android.content.pm;
+import static com.android.internal.pm.pkg.parsing.ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES;
+
import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.CheckResult;
@@ -55,7 +57,6 @@ import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.dex.ArtManager;
-import android.content.pm.pkg.FrameworkPackageUserState;
import android.content.pm.verify.domain.DomainVerificationManager;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -91,6 +92,10 @@ import android.util.AndroidException;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.parsing.PackageInfoCommonUtils;
+import com.android.internal.pm.parsing.PackageParser2;
+import com.android.internal.pm.parsing.PackageParserException;
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DataClass;
@@ -817,6 +822,8 @@ public abstract class PackageManager {
GET_DISABLED_UNTIL_USED_COMPONENTS,
GET_UNINSTALLED_PACKAGES,
MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS,
+ MATCH_DIRECT_BOOT_AWARE,
+ MATCH_DIRECT_BOOT_UNAWARE,
GET_ATTRIBUTIONS_LONG,
})
@Retention(RetentionPolicy.SOURCE)
@@ -2518,6 +2525,7 @@ public abstract class PackageManager {
USER_MIN_ASPECT_RATIO_16_9,
USER_MIN_ASPECT_RATIO_3_2,
USER_MIN_ASPECT_RATIO_FULLSCREEN,
+ USER_MIN_ASPECT_RATIO_APP_DEFAULT,
})
@Retention(RetentionPolicy.SOURCE)
public @interface UserMinAspectRatio {}
@@ -2571,6 +2579,16 @@ public abstract class PackageManager {
*/
public static final int USER_MIN_ASPECT_RATIO_FULLSCREEN = 6;
+ /**
+ * Aspect ratio override code: user sets to app's default aspect ratio.
+ * This resets both the user-forced aspect ratio, and the device manufacturer
+ * per-app override {@link ActivityInfo#OVERRIDE_ANY_ORIENTATION_TO_USER}.
+ * It is different from {@link #USER_MIN_ASPECT_RATIO_UNSET} as the latter may
+ * apply the device manufacturer per-app orientation override if any,
+ * @hide
+ */
+ public static final int USER_MIN_ASPECT_RATIO_APP_DEFAULT = 7;
+
/** @hide */
@IntDef(flag = true, prefix = { "DELETE_" }, value = {
DELETE_KEEP_DATA,
@@ -3295,6 +3313,14 @@ public abstract class PackageManager {
/**
* Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports NFC charging.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ @FlaggedApi(android.nfc.Flags.FLAG_ENABLE_NFC_CHARGING)
+ public static final String FEATURE_NFC_CHARGING = "android.hardware.nfc.charging";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The Beam API is enabled on the device.
*/
@SdkConstant(SdkConstantType.FEATURE)
@@ -3304,7 +3330,7 @@ public abstract class PackageManager {
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device supports any
* one of the {@link #FEATURE_NFC}, {@link #FEATURE_NFC_HOST_CARD_EMULATION},
- * or {@link #FEATURE_NFC_HOST_CARD_EMULATION_NFCF} features.
+ * {@link #FEATURE_NFC_HOST_CARD_EMULATION_NFCF}, or {@link #FEATURE_NFC_CHARGING} features.
*
* @hide
*/
@@ -8609,28 +8635,56 @@ public abstract class PackageManager {
@Nullable
public PackageInfo getPackageArchiveInfo(@NonNull String archiveFilePath,
@NonNull PackageInfoFlags flags) {
- long flagsBits = flags.getValue();
- final PackageParser parser = new PackageParser();
- parser.setCallback(new PackageParser.CallbackImpl(this));
final File apkFile = new File(archiveFilePath);
+
+ @PackageInfoFlagsBits long flagsBits = flags.getValue();
+ if ((flagsBits & (MATCH_DIRECT_BOOT_UNAWARE | MATCH_DIRECT_BOOT_AWARE)) != 0) {
+ // Caller expressed an explicit opinion about what encryption
+ // aware/unaware components they want to see, so fall through and
+ // give them what they want
+ } else {
+ // Caller expressed no opinion, so match everything
+ flagsBits |= MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE;
+ }
+
+ int parserFlags = 0;
+ if ((flagsBits & (GET_SIGNATURES | GET_SIGNING_CERTIFICATES)) != 0) {
+ parserFlags |= PARSE_COLLECT_CERTIFICATES;
+ }
+
+ final PackageParser2 parser2 = new PackageParser2(/*separateProcesses*/ null,
+ /*displayMetrics*/ null,/*cacher*/ null,
+ new PackageParser2.Callback() {
+ @Override
+ public boolean hasFeature(String feature) {
+ return PackageManager.this.hasSystemFeature(feature);
+ }
+
+ @NonNull
+ @Override
+ public Set<String> getHiddenApiWhitelistedApps() {
+ return Collections.emptySet();
+ }
+
+ @NonNull
+ @Override
+ public Set<String> getInstallConstraintsAllowlist() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public boolean isChangeEnabled(long changeId,
+ @androidx.annotation.NonNull ApplicationInfo appInfo) {
+ return false;
+ }
+ });
+
try {
- if ((flagsBits & (MATCH_DIRECT_BOOT_UNAWARE | MATCH_DIRECT_BOOT_AWARE)) != 0) {
- // Caller expressed an explicit opinion about what encryption
- // aware/unaware components they want to see, so fall through and
- // give them what they want
- } else {
- // Caller expressed no opinion, so match everything
- flagsBits |= MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE;
- }
+ ParsedPackage pp = parser2.parsePackage(apkFile, parserFlags, false);
- PackageParser.Package pkg = parser.parsePackage(apkFile, 0, false);
- if ((flagsBits & GET_SIGNATURES) != 0 || (flagsBits & GET_SIGNING_CERTIFICATES) != 0) {
- PackageParser.collectCertificates(pkg, false /* skipVerify */);
- }
- return PackageParser.generatePackageInfo(pkg, null, (int) flagsBits, 0, 0, null,
- FrameworkPackageUserState.DEFAULT);
- } catch (PackageParser.PackageParserException e) {
- Log.w(TAG, "Failure to parse package archive", e);
+ return PackageInfoCommonUtils.generate(pp, flagsBits, UserHandle.myUserId());
+ } catch (PackageParserException e) {
+ Log.w(TAG, "Failure to parse package archive apkFile= " +apkFile);
return null;
}
}
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 60d5c140e2b3..94bec3562bb0 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -123,3 +123,11 @@ flag {
bug: "306329516"
is_fixed_read_only: true
}
+
+flag {
+ name: "improve_home_app_behavior"
+ namespace: "package_manager_service"
+ description: "Feature flag to improve the uninstallation and preferred activity of home app."
+ bug: "310801107"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 9a1796fd3d97..c7797c719e2c 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -64,3 +64,10 @@ flag {
bug: "296829976"
is_fixed_read_only: true
}
+
+flag {
+ name: "allow_resolver_sheet_for_private_space"
+ namespace: "profile_experiences"
+ description: "Add support for Private Space in resolver sheet"
+ bug: "307515485"
+} \ No newline at end of file
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 40e03dbb8b34..60ad8e81fcf4 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -86,6 +86,8 @@ public class SystemSensorManager extends SensorManager {
private static native long nativeCreate(String opPackageName);
private static native boolean nativeGetSensorAtIndex(long nativeInstance,
Sensor sensor, int index);
+ private static native boolean nativeGetDefaultDeviceSensorAtIndex(long nativeInstance,
+ Sensor sensor, int index);
private static native void nativeGetDynamicSensors(long nativeInstance, List<Sensor> list);
private static native void nativeGetRuntimeSensors(
long nativeInstance, int deviceId, List<Sensor> list);
@@ -162,11 +164,14 @@ public class SystemSensorManager extends SensorManager {
// initialize the sensor list
for (int index = 0;; ++index) {
Sensor sensor = new Sensor();
- if (!nativeGetSensorAtIndex(mNativeInstance, sensor, index)) break;
+ if (android.companion.virtual.flags.Flags.enableNativeVdm()) {
+ if (!nativeGetDefaultDeviceSensorAtIndex(mNativeInstance, sensor, index)) break;
+ } else {
+ if (!nativeGetSensorAtIndex(mNativeInstance, sensor, index)) break;
+ }
mFullSensorsList.add(sensor);
mHandleToSensor.put(sensor.getHandle(), sensor);
}
-
}
/** @hide */
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 3ab889ddfa5d..665d8d280bc6 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -557,13 +557,15 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* on a particular SessionConfiguration.</p>
*
* @return List of CameraCharacteristic keys containing characterisitics specific to a session
- * configuration. For Android 15, this list only contains CONTROL_ZOOM_RATIO_RANGE.
+ * configuration. For Android 15, this list only contains CONTROL_ZOOM_RATIO_RANGE and
+ * SCALER_AVAILABLE_MAX_DIGITAL_ZOOM.
*/
@NonNull
@FlaggedApi(Flags.FLAG_FEATURE_COMBINATION_QUERY)
public List<CameraCharacteristics.Key<?>> getAvailableSessionCharacteristicsKeys() {
if (mAvailableSessionCharacteristicsKeys == null) {
- mAvailableSessionCharacteristicsKeys = Arrays.asList(CONTROL_ZOOM_RATIO_RANGE);
+ mAvailableSessionCharacteristicsKeys =
+ Arrays.asList(CONTROL_ZOOM_RATIO_RANGE, SCALER_AVAILABLE_MAX_DIGITAL_ZOOM);
}
return mAvailableSessionCharacteristicsKeys;
}
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index ffd7212a7525..64a62a9299b8 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -751,6 +751,23 @@ public abstract class DisplayManagerInternal {
*/
boolean blockScreenOn(Runnable unblocker);
+ /**
+ * Get the brightness levels used to determine automatic brightness based on lux levels.
+ * @param mode The auto-brightness mode
+ * (AutomaticBrightnessController.AutomaticBrightnessMode)
+ * @return The brightness levels for the specified mode. The values are between
+ * {@link PowerManager.BRIGHTNESS_MIN} and {@link PowerManager.BRIGHTNESS_MAX}.
+ */
+ float[] getAutoBrightnessLevels(int mode);
+
+ /**
+ * Get the lux levels used to determine automatic brightness.
+ * @param mode The auto-brightness mode
+ * (AutomaticBrightnessController.AutomaticBrightnessMode)
+ * @return The lux levels for the specified mode
+ */
+ float[] getAutoBrightnessLuxLevels(int mode);
+
/** Returns whether displayoffload supports the given display state. */
static boolean isSupportedOffloadState(int displayState) {
return Display.isSuspendedState(displayState);
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index f6beec179d57..967a0cc92ef5 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -30,8 +30,10 @@ import android.nfc.INfcFCardEmulation;
import android.nfc.INfcUnlockHandler;
import android.nfc.ITagRemovedCallback;
import android.nfc.INfcDta;
+import android.nfc.INfcWlcStateListener;
import android.nfc.NfcAntennaInfo;
import android.os.Bundle;
+import android.nfc.WlcLDeviceInfo;
/**
* @hide
@@ -86,4 +88,11 @@ interface INfcAdapter
boolean enableReaderOption(boolean enable);
boolean isObserveModeSupported();
boolean setObserveMode(boolean enabled);
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
+ boolean enableWlc(boolean enable);
+ boolean isWlcEnabled();
+ void registerWlcStateListener(in INfcWlcStateListener listener);
+ void unregisterWlcStateListener(in INfcWlcStateListener listener);
+ WlcLDeviceInfo getWlcLDeviceInfo();
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallReady.java b/core/java/android/nfc/INfcWlcStateListener.aidl
index 548f2c544da7..c2b7075bc6e4 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallReady.java
+++ b/core/java/android/nfc/INfcWlcStateListener.aidl
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright 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
*
- * https://www.apache.org/licenses/LICENSE-2.0
+ * 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,
@@ -14,14 +14,17 @@
* limitations under the License.
*/
-package com.android.packageinstaller.v2.model.installstagedata;
+package android.nfc;
-public class InstallReady extends InstallStage{
-
- private final int mStage = InstallStage.STAGE_READY;
-
- @Override
- public int getStageCode() {
- return mStage;
- }
+import android.nfc.WlcLDeviceInfo;
+/**
+ * @hide
+ */
+oneway interface INfcWlcStateListener {
+ /**
+ * Called whenever NFC WLC state changes
+ *
+ * @param wlcLDeviceInfo NFC wlc listener information
+ */
+ void onWlcStateChanged(in WlcLDeviceInfo wlcLDeviceInfo);
}
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index f407fb73534f..21e23ae53979 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -75,6 +75,7 @@ public final class NfcAdapter {
static final String TAG = "NFC";
private final NfcControllerAlwaysOnListener mControllerAlwaysOnListener;
+ private final NfcWlcStateListener mNfcWlcStateListener;
/**
* Intent to start an activity when a tag with NDEF payload is discovered.
@@ -440,6 +441,7 @@ public final class NfcAdapter {
static boolean sIsInitialized = false;
static boolean sHasNfcFeature;
static boolean sHasCeFeature;
+ static boolean sHasNfcWlcFeature;
// Final after first constructor, except for
// attemptDeadServiceRecovery() when NFC crashes - we accept a best effort
@@ -650,8 +652,9 @@ public final class NfcAdapter {
|| pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF)
|| pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC)
|| pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE);
+ sHasNfcWlcFeature = pm.hasSystemFeature(PackageManager.FEATURE_NFC_CHARGING);
/* is this device meant to have NFC */
- if (!sHasNfcFeature && !sHasCeFeature) {
+ if (!sHasNfcFeature && !sHasCeFeature && !sHasNfcWlcFeature) {
Log.v(TAG, "this device does not have NFC support");
throw new UnsupportedOperationException();
}
@@ -776,6 +779,7 @@ public final class NfcAdapter {
mTagRemovedListener = null;
mLock = new Object();
mControllerAlwaysOnListener = new NfcControllerAlwaysOnListener(getService());
+ mNfcWlcStateListener = new NfcWlcStateListener(getService());
}
/**
@@ -944,7 +948,8 @@ public final class NfcAdapter {
Log.e(TAG, "Failed to recover NFC Service.");
}
}
- return serviceState && (isTagReadingEnabled() || isCardEmulationEnabled());
+ return serviceState
+ && (isTagReadingEnabled() || isCardEmulationEnabled() || sHasNfcWlcFeature);
}
/**
@@ -2587,4 +2592,159 @@ public final class NfcAdapter {
return false;
}
}
+
+ /**
+ * Sets NFC charging feature.
+ * <p>This API is for the Settings application.
+ * @return True if successful
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ public boolean enableWlc(boolean enable) {
+ if (!sHasNfcWlcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ try {
+ return sService.enableWlc(enable);
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return false;
+ }
+ try {
+ return sService.enableWlc(enable);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Checks NFC charging feature is enabled.
+ *
+ * @return True if NFC charging is enabled, false otherwise
+ * @throws UnsupportedOperationException if FEATURE_NFC_CHARGING
+ * is unavailable
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+ public boolean isWlcEnabled() {
+ if (!sHasNfcWlcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ try {
+ return sService.isWlcEnabled();
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return false;
+ }
+ try {
+ return sService.isWlcEnabled();
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ return false;
+ }
+ }
+
+ /**
+ * A listener to be invoked when NFC controller always on state changes.
+ * <p>Register your {@code ControllerAlwaysOnListener} implementation with {@link
+ * NfcAdapter#registerWlcStateListener} and disable it with {@link
+ * NfcAdapter#unregisterWlcStateListenerListener}.
+ * @see #registerWlcStateListener
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+ public interface WlcStateListener {
+ /**
+ * Called on NFC WLC state changes
+ */
+ void onWlcStateChanged(@NonNull WlcLDeviceInfo wlcLDeviceInfo);
+ }
+
+ /**
+ * Register a {@link WlcStateListener} to listen for NFC WLC state changes
+ * <p>The provided listener will be invoked by the given {@link Executor}.
+ *
+ * @param executor an {@link Executor} to execute given listener
+ * @param listener user implementation of the {@link WlcStateListener}
+ * @throws UnsupportedOperationException if FEATURE_NFC_CHARGING
+ * is unavailable
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+ public void registerWlcStateListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull WlcStateListener listener) {
+ if (!sHasNfcWlcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ mNfcWlcStateListener.register(executor, listener);
+ }
+
+ /**
+ * Unregister the specified {@link WlcStateListener}
+ * <p>The same {@link WlcStateListener} object used when calling
+ * {@link #registerWlcStateListener(Executor, WlcStateListener)}
+ * must be used.
+ *
+ * <p>Listeners are automatically unregistered when application process goes away
+ *
+ * @param listener user implementation of the {@link WlcStateListener}a
+ * @throws UnsupportedOperationException if FEATURE_NFC_CHARGING
+ * is unavailable
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+ public void unregisterWlcStateListener(
+ @NonNull WlcStateListener listener) {
+ if (!sHasNfcWlcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ mNfcWlcStateListener.unregister(listener);
+ }
+
+ /**
+ * Returns information on the NFC charging listener device
+ *
+ * @return Information on the NFC charging listener device
+ * @throws UnsupportedOperationException if FEATURE_NFC_CHARGING
+ * is unavailable
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+ @Nullable
+ public WlcLDeviceInfo getWlcLDeviceInfo() {
+ if (!sHasNfcWlcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ try {
+ return sService.getWlcLDeviceInfo();
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return null;
+ }
+ try {
+ return sService.getWlcLDeviceInfo();
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ return null;
+ }
+ }
}
diff --git a/core/java/android/nfc/NfcWlcStateListener.java b/core/java/android/nfc/NfcWlcStateListener.java
new file mode 100644
index 000000000000..8d793101f41f
--- /dev/null
+++ b/core/java/android/nfc/NfcWlcStateListener.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 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 android.nfc;
+
+import android.annotation.NonNull;
+import android.nfc.NfcAdapter.WlcStateListener;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * @hide
+ */
+public class NfcWlcStateListener extends INfcWlcStateListener.Stub {
+ private static final String TAG = NfcWlcStateListener.class.getSimpleName();
+
+ private final INfcAdapter mAdapter;
+
+ private final Map<WlcStateListener, Executor> mListenerMap = new HashMap<>();
+
+ private WlcLDeviceInfo mCurrentState = null;
+ private boolean mIsRegistered = false;
+
+ public NfcWlcStateListener(@NonNull INfcAdapter adapter) {
+ mAdapter = adapter;
+ }
+
+ /**
+ * Register a {@link WlcStateListener} with this
+ * {@link WlcStateListener}
+ *
+ * @param executor an {@link Executor} to execute given listener
+ * @param listener user implementation of the {@link WlcStateListener}
+ */
+ public void register(@NonNull Executor executor, @NonNull WlcStateListener listener) {
+ synchronized (this) {
+ if (mListenerMap.containsKey(listener)) {
+ return;
+ }
+
+ mListenerMap.put(listener, executor);
+
+ if (!mIsRegistered) {
+ try {
+ mAdapter.registerWlcStateListener(this);
+ mIsRegistered = true;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to register");
+ }
+ }
+ }
+ }
+
+ /**
+ * Unregister the specified {@link WlcStateListener}
+ *
+ * @param listener user implementation of the {@link WlcStateListener}
+ */
+ public void unregister(@NonNull WlcStateListener listener) {
+ synchronized (this) {
+ if (!mListenerMap.containsKey(listener)) {
+ return;
+ }
+
+ mListenerMap.remove(listener);
+
+ if (mListenerMap.isEmpty() && mIsRegistered) {
+ try {
+ mAdapter.unregisterWlcStateListener(this);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to unregister");
+ }
+ mIsRegistered = false;
+ }
+ }
+ }
+
+ private void sendCurrentState(@NonNull WlcStateListener listener) {
+ synchronized (this) {
+ Executor executor = mListenerMap.get(listener);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> listener.onWlcStateChanged(
+ mCurrentState));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ @Override
+ public void onWlcStateChanged(@NonNull WlcLDeviceInfo wlcLDeviceInfo) {
+ synchronized (this) {
+ mCurrentState = wlcLDeviceInfo;
+
+ for (WlcStateListener cb : mListenerMap.keySet()) {
+ sendCurrentState(cb);
+ }
+ }
+ }
+}
+
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStaging.java b/core/java/android/nfc/WlcLDeviceInfo.aidl
index a979cf87c350..33143fe81162 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStaging.java
+++ b/core/java/android/nfc/WlcLDeviceInfo.aidl
@@ -14,14 +14,6 @@
* limitations under the License.
*/
-package com.android.packageinstaller.v2.model.installstagedata;
+package android.nfc;
-public class InstallStaging extends InstallStage {
-
- private final int mStage = InstallStage.STAGE_STAGING;
-
- @Override
- public int getStageCode() {
- return mStage;
- }
-}
+parcelable WlcLDeviceInfo;
diff --git a/core/java/android/nfc/WlcLDeviceInfo.java b/core/java/android/nfc/WlcLDeviceInfo.java
new file mode 100644
index 000000000000..016431e90d8e
--- /dev/null
+++ b/core/java/android/nfc/WlcLDeviceInfo.java
@@ -0,0 +1,107 @@
+/*
+ * 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 android.nfc;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Contains information of the nfc wireless charging listener device information.
+ */
+@FlaggedApi(Flags.FLAG_ENABLE_NFC_CHARGING)
+public final class WlcLDeviceInfo implements Parcelable {
+ public static final int DISCONNECTED = 1;
+
+ public static final int CONNECTED_CHARGING = 2;
+
+ public static final int CONNECTED_DISCHARGING = 3;
+
+ private double mProductId;
+ private double mTemperature;
+ private double mBatteryLevel;
+ private int mState;
+
+ public WlcLDeviceInfo(double productId, double temperature, double batteryLevel, int state) {
+ this.mProductId = productId;
+ this.mTemperature = temperature;
+ this.mBatteryLevel = batteryLevel;
+ this.mState = state;
+ }
+
+ /**
+ * ProductId of the WLC listener device.
+ */
+ public double getProductId() {
+ return mProductId;
+ }
+
+ /**
+ * Temperature of the WLC listener device.
+ */
+ public double getTemperature() {
+ return mTemperature;
+ }
+
+ /**
+ * BatteryLevel of the WLC listener device.
+ */
+ public double getBatteryLevel() {
+ return mBatteryLevel;
+ }
+
+ /**
+ * State of the WLC listener device.
+ */
+ public int getState() {
+ return mState;
+ }
+
+ private WlcLDeviceInfo(Parcel in) {
+ this.mProductId = in.readDouble();
+ this.mTemperature = in.readDouble();
+ this.mBatteryLevel = in.readDouble();
+ this.mState = in.readInt();
+ }
+
+ public static final @NonNull Parcelable.Creator<WlcLDeviceInfo> CREATOR =
+ new Parcelable.Creator<WlcLDeviceInfo>() {
+ @Override
+ public WlcLDeviceInfo createFromParcel(Parcel in) {
+ return new WlcLDeviceInfo(in);
+ }
+
+ @Override
+ public WlcLDeviceInfo[] newArray(int size) {
+ return new WlcLDeviceInfo[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeDouble(mProductId);
+ dest.writeDouble(mTemperature);
+ dest.writeDouble(mBatteryLevel);
+ dest.writeDouble(mState);
+ }
+}
diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
index bd087f970426..41dee3ab035c 100644
--- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -21,10 +21,10 @@
package android.nfc.cardemulation;
import android.annotation.FlaggedApi;
-import android.compat.annotation.UnsupportedAppUsage;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -374,7 +374,7 @@ public final class ApduServiceInfo implements Parcelable {
// Set uid
mUid = si.applicationInfo.uid;
- mCategoryOtherServiceEnabled = false; // support other category
+ mCategoryOtherServiceEnabled = true; // support other category
}
diff --git a/core/java/android/nfc/flags.aconfig b/core/java/android/nfc/flags.aconfig
index 17e042761dbe..ce4f77725ef1 100644
--- a/core/java/android/nfc/flags.aconfig
+++ b/core/java/android/nfc/flags.aconfig
@@ -48,3 +48,17 @@ flag {
description: "Enable NFC Polling Loop Notifications ST shim"
bug: "294217286"
}
+
+flag {
+ name: "enable_tag_detection_broadcasts"
+ namespace: "nfc"
+ description: "Enable sending broadcasts to Wallet role holder when a tag enters/leaves the field."
+ bug: "306203494"
+}
+
+flag {
+ name: "enable_nfc_charging"
+ namespace: "nfc"
+ description: "Flag for NFC charging changes"
+ bug: "292143899"
+}
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index fc8523ee23dd..80ec458cd715 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -757,7 +757,6 @@ public class Process {
@Nullable String invokeWith,
@Nullable String packageName,
@Nullable long[] disabledCompatChanges,
- boolean bindMountSyspropOverrides,
@Nullable String[] zygoteArgs) {
// Webview zygote can't access app private data files, so doesn't need to know its data
// info.
@@ -767,7 +766,7 @@ public class Process {
/*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, /*isTopApp=*/ false,
disabledCompatChanges, /* pkgDataInfoMap */ null,
/* whitelistedDataInfoMap */ null, /* bindMountAppsData */ false,
- /* bindMountAppStorageDirs */ false, bindMountSyspropOverrides, zygoteArgs);
+ /* bindMountAppStorageDirs */ false, /* bindMountSyspropOverrides */ false, zygoteArgs);
}
/**
diff --git a/core/java/android/os/ServiceSpecificException.java b/core/java/android/os/ServiceSpecificException.java
index 49ce40bb6ee9..df503e86a700 100644
--- a/core/java/android/os/ServiceSpecificException.java
+++ b/core/java/android/os/ServiceSpecificException.java
@@ -18,6 +18,7 @@ package android.os;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
/**
* An exception specific to a service.
@@ -33,6 +34,7 @@ import android.annotation.SystemApi;
* @hide
*/
@SystemApi
+@RavenwoodKeepWholeClass
public class ServiceSpecificException extends RuntimeException {
public final int errorCode;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 4af657dab0cd..54cc5f471c33 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -14339,6 +14339,14 @@ public final class Settings {
public static final String MODE_RINGER = "mode_ringer";
/**
+ * Whether or not Alarm stream should always be muted with Ringer.
+ *
+ * @hide
+ */
+ public static final String MUTE_ALARM_STREAM_WITH_RINGER_MODE =
+ "mute_alarm_stream_with_ringer_mode";
+
+ /**
* Overlay display devices setting.
* The associated value is a specially formatted string that describes the
* size and density of simulated secondary display devices.
@@ -15013,6 +15021,16 @@ public final class Settings {
"foreground_service_starts_logging_enabled";
/**
+ * Describes whether AM's AppProfiler should collect PSS even if RSS is the default. This
+ * can be set by a user in developer settings.
+ * Default: 0
+ * @hide
+ */
+ @Readable
+ public static final String FORCE_ENABLE_PSS_PROFILING =
+ "force_enable_pss_profiling";
+
+ /**
* @hide
* @see com.android.server.appbinding.AppBindingConstants
*/
@@ -19620,6 +19638,15 @@ public final class Settings {
*/
public static final String WEAR_POWER_ANOMALY_SERVICE_ENABLED =
"wear_power_anomaly_service_enabled";
+
+ /**
+ * A boolean that tracks whether Wrist Detection Auto-Locking is enabled.
+ *
+ * @hide
+ */
+ @Readable
+ public static final String WRIST_DETECTION_AUTO_LOCKING_ENABLED =
+ "wear_wrist_detection_auto_locking_enabled";
}
}
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index b56bef3c93a0..30524a1132fa 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -50,3 +50,11 @@ flag {
description: "Collect sepolicy hash from sysfs"
bug: "308471499"
}
+
+flag {
+ name: "frp_enforcement"
+ namespace: "android_hw_security"
+ description: "This flag controls whether PDB enforces FRP"
+ bug: "290312729"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index cabab6c80bf5..0927d4519b9c 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -17,7 +17,6 @@
package android.view;
import static android.view.InsetsSourceProto.FRAME;
-import static android.view.InsetsSourceProto.TYPE;
import static android.view.InsetsSourceProto.TYPE_NUMBER;
import static android.view.InsetsSourceProto.VISIBLE;
import static android.view.InsetsSourceProto.VISIBLE_FRAME;
@@ -353,13 +352,12 @@ public class InsetsSource implements Parcelable {
*/
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
- proto.write(TYPE, WindowInsets.Type.toString(mType));
- proto.write(TYPE_NUMBER, mType);
mFrame.dumpDebug(proto, FRAME);
if (mVisibleFrame != null) {
mVisibleFrame.dumpDebug(proto, VISIBLE_FRAME);
}
proto.write(VISIBLE, mVisible);
+ proto.write(TYPE_NUMBER, mType);
proto.end(token);
}
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index 34b288460a67..0ce61bb17774 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -21,11 +21,11 @@ import static android.view.InsetsController.AnimationType;
import static android.view.InsetsController.DEBUG;
import static android.view.InsetsSourceConsumerProto.ANIMATION_STATE;
import static android.view.InsetsSourceConsumerProto.HAS_WINDOW_FOCUS;
-import static android.view.InsetsSourceConsumerProto.INTERNAL_INSETS_TYPE;
import static android.view.InsetsSourceConsumerProto.IS_REQUESTED_VISIBLE;
import static android.view.InsetsSourceConsumerProto.PENDING_FRAME;
import static android.view.InsetsSourceConsumerProto.PENDING_VISIBLE_FRAME;
import static android.view.InsetsSourceConsumerProto.SOURCE_CONTROL;
+import static android.view.InsetsSourceConsumerProto.TYPE_NUMBER;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
@@ -393,7 +393,6 @@ public class InsetsSourceConsumer {
void dumpDebug(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
- proto.write(INTERNAL_INSETS_TYPE, WindowInsets.Type.toString(mType));
proto.write(HAS_WINDOW_FOCUS, mHasWindowFocus);
proto.write(IS_REQUESTED_VISIBLE, isShowRequested());
if (mSourceControl != null) {
@@ -406,6 +405,7 @@ public class InsetsSourceConsumer {
mPendingVisibleFrame.dumpDebug(proto, PENDING_VISIBLE_FRAME);
}
proto.write(ANIMATION_STATE, mAnimationState);
+ proto.write(TYPE_NUMBER, mType);
proto.end(token);
}
}
diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java
index 7ea93f5a7542..527c7ed8e5f6 100644
--- a/core/java/android/view/InsetsSourceControl.java
+++ b/core/java/android/view/InsetsSourceControl.java
@@ -20,7 +20,7 @@ import static android.graphics.PointProto.X;
import static android.graphics.PointProto.Y;
import static android.view.InsetsSourceControlProto.LEASH;
import static android.view.InsetsSourceControlProto.POSITION;
-import static android.view.InsetsSourceControlProto.TYPE;
+import static android.view.InsetsSourceControlProto.TYPE_NUMBER;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -244,8 +244,6 @@ public class InsetsSourceControl implements Parcelable {
*/
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
- proto.write(TYPE, WindowInsets.Type.toString(mType));
-
final long surfaceToken = proto.start(POSITION);
proto.write(X, mSurfacePosition.x);
proto.write(Y, mSurfacePosition.y);
@@ -254,6 +252,8 @@ public class InsetsSourceControl implements Parcelable {
if (mLeash != null) {
mLeash.dumpDebug(proto, LEASH);
}
+
+ proto.write(TYPE_NUMBER, mType);
proto.end(token);
}
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 57a41619ff8d..921afaa99277 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -38,6 +38,8 @@ import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Intent;
import android.graphics.Insets;
@@ -1519,6 +1521,9 @@ public final class WindowInsets {
}
/** @hide */
+ @TestApi
+ @NonNull
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
public static String toString(@InsetsType int types) {
StringBuilder result = new StringBuilder();
if ((types & STATUS_BARS) != 0) {
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index ac9ad2dc585d..feccc6bef7a4 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2320,6 +2320,15 @@ public final class InputMethodManager {
* @hide
*/
public boolean hideSoftInputFromView(@NonNull View view, @HideFlags int flags) {
+ final boolean isFocusedAndWindowFocused = view.hasWindowFocus() && view.isFocused();
+ synchronized (mH) {
+ if (!isFocusedAndWindowFocused && !hasServedByInputMethodLocked(view)) {
+ // Fail early if the view is not focused and not served
+ // to avoid logging many erroneous calls.
+ return false;
+ }
+ }
+
final var reason = SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_VIEW;
final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestHide(
null /* component */, Process.myUid(),
diff --git a/core/java/android/webkit/URLUtil.java b/core/java/android/webkit/URLUtil.java
index c7609a6c18af..828ec265f4c8 100644
--- a/core/java/android/webkit/URLUtil.java
+++ b/core/java/android/webkit/URLUtil.java
@@ -44,9 +44,7 @@ public final class URLUtil {
static final String PROXY_BASE = "file:///cookieless_proxy/";
static final String CONTENT_BASE = "content:";
- /**
- * Cleans up (if possible) user-entered web addresses
- */
+ /** Cleans up (if possible) user-entered web addresses */
public static String guessUrl(String inUrl) {
String retVal = inUrl;
@@ -86,8 +84,12 @@ public final class URLUtil {
return webAddress.toString();
}
- public static String composeSearchUrl(String inQuery, String template,
- String queryPlaceHolder) {
+ /**
+ * Inserts the {@code inQuery} in the {@code template} after URL-encoding it. The encoded query
+ * will replace the {@code queryPlaceHolder}.
+ */
+ public static String composeSearchUrl(
+ String inQuery, String template, String queryPlaceHolder) {
int placeHolderIndex = template.indexOf(queryPlaceHolder);
if (placeHolderIndex < 0) {
return null;
@@ -104,8 +106,7 @@ public final class URLUtil {
return null;
}
- buffer.append(template.substring(
- placeHolderIndex + queryPlaceHolder.length()));
+ buffer.append(template.substring(placeHolderIndex + queryPlaceHolder.length()));
return buffer.toString();
}
@@ -123,8 +124,7 @@ public final class URLUtil {
byte b = url[i];
if (b == '%') {
if (url.length - i > 2) {
- b = (byte) (parseHex(url[i + 1]) * 16
- + parseHex(url[i + 2]));
+ b = (byte) (parseHex(url[i + 1]) * 16 + parseHex(url[i + 2]));
i += 2;
} else {
throw new IllegalArgumentException("Invalid format");
@@ -189,8 +189,8 @@ public final class URLUtil {
}
/**
- * @return {@code true} if the url is a proxy url to allow cookieless network
- * requests from a file url.
+ * @return {@code true} if the url is a proxy url to allow cookieless network requests from a
+ * file url.
* @deprecated Cookieless proxy is no longer supported.
*/
@Deprecated
@@ -202,9 +202,10 @@ public final class URLUtil {
* @return {@code true} if the url is a local file.
*/
public static boolean isFileUrl(String url) {
- return (null != url) && (url.startsWith(FILE_BASE) &&
- !url.startsWith(ASSET_BASE) &&
- !url.startsWith(PROXY_BASE));
+ return (null != url)
+ && (url.startsWith(FILE_BASE)
+ && !url.startsWith(ASSET_BASE)
+ && !url.startsWith(PROXY_BASE));
}
/**
@@ -232,18 +233,18 @@ public final class URLUtil {
* @return {@code true} if the url is an http: url.
*/
public static boolean isHttpUrl(String url) {
- return (null != url) &&
- (url.length() > 6) &&
- url.substring(0, 7).equalsIgnoreCase("http://");
+ return (null != url)
+ && (url.length() > 6)
+ && url.substring(0, 7).equalsIgnoreCase("http://");
}
/**
* @return {@code true} if the url is an https: url.
*/
public static boolean isHttpsUrl(String url) {
- return (null != url) &&
- (url.length() > 7) &&
- url.substring(0, 8).equalsIgnoreCase("https://");
+ return (null != url)
+ && (url.length() > 7)
+ && url.substring(0, 8).equalsIgnoreCase("https://");
}
/**
@@ -271,19 +272,17 @@ public final class URLUtil {
return false;
}
- return (isAssetUrl(url) ||
- isResourceUrl(url) ||
- isFileUrl(url) ||
- isAboutUrl(url) ||
- isHttpUrl(url) ||
- isHttpsUrl(url) ||
- isJavaScriptUrl(url) ||
- isContentUrl(url));
+ return (isAssetUrl(url)
+ || isResourceUrl(url)
+ || isFileUrl(url)
+ || isAboutUrl(url)
+ || isHttpUrl(url)
+ || isHttpsUrl(url)
+ || isJavaScriptUrl(url)
+ || isContentUrl(url));
}
- /**
- * Strips the url of the anchor.
- */
+ /** Strips the url of the anchor. */
public static String stripAnchor(String url) {
int anchorIndex = url.indexOf('#');
if (anchorIndex != -1) {
@@ -293,19 +292,16 @@ public final class URLUtil {
}
/**
- * Guesses canonical filename that a download would have, using
- * the URL and contentDisposition. File extension, if not defined,
- * is added based on the mimetype
+ * Guesses canonical filename that a download would have, using the URL and contentDisposition.
+ * File extension, if not defined, is added based on the mimetype
+ *
* @param url Url to the content
* @param contentDisposition Content-Disposition HTTP header or {@code null}
* @param mimeType Mime-type of the content or {@code null}
- *
* @return suggested filename
*/
public static final String guessFileName(
- String url,
- @Nullable String contentDisposition,
- @Nullable String mimeType) {
+ String url, @Nullable String contentDisposition, @Nullable String mimeType) {
String filename = null;
String extension = null;
@@ -369,8 +365,9 @@ public final class URLUtil {
// Compare the last segment of the extension against the mime type.
// If there's a mismatch, discard the entire extension.
int lastDotIndex = filename.lastIndexOf('.');
- String typeFromExt = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
- filename.substring(lastDotIndex + 1));
+ String typeFromExt =
+ MimeTypeMap.getSingleton()
+ .getMimeTypeFromExtension(filename.substring(lastDotIndex + 1));
if (typeFromExt != null && !typeFromExt.equalsIgnoreCase(mimeType)) {
extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
if (extension != null) {
@@ -389,17 +386,17 @@ public final class URLUtil {
/** Regex used to parse content-disposition headers */
private static final Pattern CONTENT_DISPOSITION_PATTERN =
- Pattern.compile("attachment;\\s*filename\\s*=\\s*(\"?)([^\"]*)\\1\\s*$",
- Pattern.CASE_INSENSITIVE);
+ Pattern.compile(
+ "attachment;\\s*filename\\s*=\\s*(\"?)([^\"]*)\\1\\s*$",
+ Pattern.CASE_INSENSITIVE);
/**
- * Parse the Content-Disposition HTTP Header. The format of the header
- * is defined here: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html
- * This header provides a filename for content that is going to be
- * downloaded to the file system. We only support the attachment type.
- * Note that RFC 2616 specifies the filename value must be double-quoted.
- * Unfortunately some servers do not quote the value so to maintain
- * consistent behaviour with other browsers, we allow unquoted values too.
+ * Parse the Content-Disposition HTTP Header. The format of the header is defined here:
+ * http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html This header provides a filename for
+ * content that is going to be downloaded to the file system. We only support the attachment
+ * type. Note that RFC 2616 specifies the filename value must be double-quoted. Unfortunately
+ * some servers do not quote the value so to maintain consistent behaviour with other browsers,
+ * we allow unquoted values too.
*/
@UnsupportedAppUsage
static String parseContentDisposition(String contentDisposition) {
@@ -409,7 +406,7 @@ public final class URLUtil {
return m.group(2);
}
} catch (IllegalStateException ex) {
- // This function is defined as returning null when it can't parse the header
+ // This function is defined as returning null when it can't parse the header
}
return null;
}
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 14c53489ba3a..d12eda35c745 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -1203,7 +1203,11 @@ public abstract class WebSettings {
* changes to this setting after that point.
*
* @param flag {@code true} if the WebView should use the database storage API
+ * @deprecated WebSQL is deprecated and this method will become a no-op on all
+ * Android versions once support is removed in Chromium. See
+ * https://developer.chrome.com/blog/deprecating-web-sql for more information.
*/
+ @Deprecated
public abstract void setDatabaseEnabled(boolean flag);
/**
@@ -1236,7 +1240,11 @@ public abstract class WebSettings {
*
* @return {@code true} if the database storage API is enabled
* @see #setDatabaseEnabled
+ * @deprecated WebSQL is deprecated and this method will become a no-op on all
+ * Android versions once support is removed in Chromium. See
+ * https://developer.chrome.com/blog/deprecating-web-sql for more information.
*/
+ @Deprecated
public abstract boolean getDatabaseEnabled();
/**
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 933cc49f8602..59d7b0e55e85 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -2,13 +2,6 @@ package: "com.android.window.flags"
# Project link: https://gantry.corp.google.com/projects/android_platform_windowing_sdk/changes
-flag {
- namespace: "windowing_sdk"
- name: "sync_window_config_update_flag"
- description: "Whether the feature to sync different window-related config updates is enabled"
- bug: "260873529"
-}
-
# Using a fixed read only flag because there are ClientTransaction scheduling before
# WindowManagerService creation.
flag {
@@ -35,13 +28,6 @@ flag {
flag {
namespace: "windowing_sdk"
- name: "window_state_resize_item_flag"
- description: "Whether to dispatch window resize through ClientTransaction is enabled"
- bug: "301870955"
-}
-
-flag {
- namespace: "windowing_sdk"
name: "fullscreen_dim_flag"
description: "Whether to allow showing fullscreen dim on ActivityEmbedding split"
bug: "253533308"
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 7534d2960b7c..7dcbbea7df90 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -249,6 +249,7 @@ public class ResolverActivity extends Activity implements
private UserHandle mCloneProfileUserHandle;
private UserHandle mTabOwnerUserHandleForLaunch;
+ private UserHandle mPrivateProfileUserHandle;
protected final LatencyTracker mLatencyTracker = getLatencyTracker();
@@ -441,6 +442,7 @@ public class ResolverActivity extends Activity implements
mPersonalProfileUserHandle = fetchPersonalProfileUserHandle();
mWorkProfileUserHandle = fetchWorkProfileUserProfile();
mCloneProfileUserHandle = fetchCloneProfileUserHandle();
+ mPrivateProfileUserHandle = fetchPrivateProfileUserHandle();
mTabOwnerUserHandleForLaunch = fetchTabOwnerUserHandleForLaunch();
// The last argument of createResolverListAdapter is whether to do special handling
@@ -648,7 +650,8 @@ public class ResolverActivity extends Activity implements
initialIntents,
rList,
filterLastUsed,
- /* userHandle */ getPersonalProfileUserHandle());
+ getPersonalProfileUserHandle());
+
QuietModeManager quietModeManager = createQuietModeManager();
return new ResolverMultiProfilePagerAdapter(
/* context */ this,
@@ -747,6 +750,9 @@ public class ResolverActivity extends Activity implements
}
protected UserHandle getPersonalProfileUserHandle() {
+ if (privateSpaceEnabled() && isLaunchedAsPrivateProfile()){
+ return mPrivateProfileUserHandle;
+ }
return mPersonalProfileUserHandle;
}
protected @Nullable UserHandle getWorkProfileUserHandle() {
@@ -761,6 +767,10 @@ public class ResolverActivity extends Activity implements
return mTabOwnerUserHandleForLaunch;
}
+ protected UserHandle getPrivateProfileUserHandle() {
+ return mPrivateProfileUserHandle;
+ }
+
protected UserHandle fetchPersonalProfileUserHandle() {
// ActivityManager.getCurrentUser() refers to the current Foreground user. When clone/work
// profile is active, we always make the personal tab from the foreground user.
@@ -795,12 +805,28 @@ public class ResolverActivity extends Activity implements
return mCloneProfileUserHandle;
}
+ protected @Nullable UserHandle fetchPrivateProfileUserHandle() {
+ mPrivateProfileUserHandle = null;
+ UserManager userManager = getSystemService(UserManager.class);
+ for (final UserInfo userInfo :
+ userManager.getProfiles(mPersonalProfileUserHandle.getIdentifier())) {
+ if (userInfo.isPrivateProfile()) {
+ mPrivateProfileUserHandle = userInfo.getUserHandle();
+ break;
+ }
+ }
+ return mPrivateProfileUserHandle;
+ }
+
private UserHandle fetchTabOwnerUserHandleForLaunch() {
- // If we are in work profile's process, return WorkProfile user as owner, otherwise we
- // always return PersonalProfile user as owner
- return UserHandle.of(UserHandle.myUserId()).equals(getWorkProfileUserHandle())
- ? getWorkProfileUserHandle()
- : getPersonalProfileUserHandle();
+ // If we are in work or private profile's process, return WorkProfile/PrivateProfile user
+ // as owner, otherwise we always return PersonalProfile user as owner
+ if (UserHandle.of(UserHandle.myUserId()).equals(getWorkProfileUserHandle())) {
+ return getWorkProfileUserHandle();
+ } else if (privateSpaceEnabled() && isLaunchedAsPrivateProfile()) {
+ return getPrivateProfileUserHandle();
+ }
+ return getPersonalProfileUserHandle();
}
private boolean hasWorkProfile() {
@@ -816,7 +842,15 @@ public class ResolverActivity extends Activity implements
&& (UserHandle.myUserId() == getCloneProfileUserHandle().getIdentifier());
}
+ protected final boolean isLaunchedAsPrivateProfile() {
+ return getPrivateProfileUserHandle() != null
+ && (UserHandle.myUserId() == getPrivateProfileUserHandle().getIdentifier());
+ }
+
protected boolean shouldShowTabs() {
+ if (privateSpaceEnabled() && isLaunchedAsPrivateProfile()) {
+ return false;
+ }
return hasWorkProfile() && ENABLE_TABBED_VIEW;
}
@@ -2619,6 +2653,11 @@ public class ResolverActivity extends Activity implements
return resolveInfo.userHandle;
}
+ private boolean privateSpaceEnabled() {
+ return mIsIntentPicker && android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.allowResolverSheetForPrivateSpace();
+ }
+
/**
* An a11y delegate that expands resolver drawer when gesture navigation reaches a partially
* invisible target in the list.
diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java
index c89cfc4b0191..5705b7ea5cf2 100644
--- a/core/java/com/android/internal/content/PackageMonitor.java
+++ b/core/java/com/android/internal/content/PackageMonitor.java
@@ -37,6 +37,7 @@ import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
+import java.lang.ref.WeakReference;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -63,6 +64,8 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
PackageMonitorCallback mPackageMonitorCallback;
+ private Executor mExecutor;
+
@UnsupportedAppUsage
public PackageMonitor() {
final boolean isCore = UserHandle.isCore(android.os.Process.myUid());
@@ -106,8 +109,8 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
if (mPackageMonitorCallback == null) {
PackageManager pm = mRegisteredContext.getPackageManager();
if (pm != null) {
- mPackageMonitorCallback = new PackageMonitorCallback(this,
- new HandlerExecutor(mRegisteredHandler));
+ mExecutor = new HandlerExecutor(mRegisteredHandler);
+ mPackageMonitorCallback = new PackageMonitorCallback(this);
int userId = user != null ? user.getIdentifier() : mRegisteredContext.getUserId();
pm.registerPackageMonitorCallback(mPackageMonitorCallback, userId);
}
@@ -131,6 +134,7 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
}
mPackageMonitorCallback = null;
mRegisteredContext = null;
+ mExecutor = null;
}
public void onBeginPackageChanges() {
@@ -362,6 +366,13 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
doHandlePackageEvent(intent);
}
+
+ private void postHandlePackageEvent(Intent intent) {
+ if (mExecutor != null) {
+ mExecutor.execute(() -> doHandlePackageEvent(intent));
+ }
+ }
+
/**
* Handle the package related event
* @param intent the intent that contains package related event information
@@ -516,13 +527,10 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
}
private static final class PackageMonitorCallback extends IRemoteCallback.Stub {
+ private final WeakReference<PackageMonitor> mMonitorWeakReference;
- private final PackageMonitor mPackageMonitor;
- private final Executor mExecutor;
-
- PackageMonitorCallback(PackageMonitor monitor, Executor executor) {
- mPackageMonitor = monitor;
- mExecutor = executor;
+ PackageMonitorCallback(PackageMonitor monitor) {
+ mMonitorWeakReference = new WeakReference<>(monitor);
}
@Override
@@ -537,7 +545,10 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
Log.w(TAG, "No intent is set for PackageMonitorCallback");
return;
}
- mExecutor.execute(() -> mPackageMonitor.doHandlePackageEvent(intent));
+ PackageMonitor monitor = mMonitorWeakReference.get();
+ if (monitor != null) {
+ monitor.postHandlePackageEvent(intent);
+ }
}
}
}
diff --git a/core/java/com/android/internal/pm/parsing/PackageInfoCommonUtils.java b/core/java/com/android/internal/pm/parsing/PackageInfoCommonUtils.java
new file mode 100644
index 000000000000..f05d9cbe1c8d
--- /dev/null
+++ b/core/java/com/android/internal/pm/parsing/PackageInfoCommonUtils.java
@@ -0,0 +1,652 @@
+/*
+ * 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.internal.pm.parsing;
+
+import static com.android.internal.pm.pkg.SEInfoUtil.COMPLETE_STR;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.Attribution;
+import android.content.pm.ComponentInfo;
+import android.content.pm.ConfigurationInfo;
+import android.content.pm.FallbackCategoryProvider;
+import android.content.pm.FeatureGroupInfo;
+import android.content.pm.FeatureInfo;
+import android.content.pm.InstrumentationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageItemInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PathPermission;
+import android.content.pm.PermissionInfo;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ServiceInfo;
+import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
+import android.content.pm.SigningInfo;
+import android.os.Debug;
+import android.os.PatternMatcher;
+import android.os.UserHandle;
+import android.util.DebugUtils;
+import android.util.Slog;
+
+import com.android.internal.pm.parsing.pkg.AndroidPackageHidden;
+import com.android.internal.pm.parsing.pkg.AndroidPackageLegacyUtils;
+import com.android.internal.pm.parsing.pkg.PackageImpl;
+import com.android.internal.pm.pkg.component.ComponentParseUtils;
+import com.android.internal.pm.pkg.component.ParsedActivity;
+import com.android.internal.pm.pkg.component.ParsedAttribution;
+import com.android.internal.pm.pkg.component.ParsedComponent;
+import com.android.internal.pm.pkg.component.ParsedInstrumentation;
+import com.android.internal.pm.pkg.component.ParsedMainComponent;
+import com.android.internal.pm.pkg.component.ParsedPermission;
+import com.android.internal.pm.pkg.component.ParsedProvider;
+import com.android.internal.pm.pkg.component.ParsedService;
+import com.android.internal.pm.pkg.component.ParsedUsesPermission;
+import com.android.internal.pm.pkg.parsing.ParsingPackageHidden;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.internal.pm.pkg.parsing.ParsingUtils;
+import com.android.internal.util.ArrayUtils;
+import com.android.server.pm.pkg.AndroidPackage;
+
+import java.util.List;
+
+/**
+ * Method that use a {@link AndroidPackage} to generate a {@link PackageInfo} though
+ * the given {@link PackageManager.PackageInfoFlags}
+ * @hide
+ **/
+// TODO(b/317215254): refactor coped code from PackageInfoUtils
+public class PackageInfoCommonUtils {
+
+ private static final String TAG = ParsingUtils.TAG;
+ private static final boolean DEBUG = false;
+
+ /**
+ * Generates a {@link PackageInfo} from the given {@link AndroidPackage}
+ */
+ @Nullable
+ public static PackageInfo generate(@Nullable AndroidPackage pkg,
+ @PackageManager.PackageInfoFlagsBits long flags, int userId) {
+ if (pkg == null) {
+ return null;
+ }
+ ApplicationInfo applicationInfo = generateApplicationInfo(pkg, flags, userId);
+
+ PackageInfo info = new PackageInfo();
+ info.packageName = pkg.getPackageName();
+ info.splitNames = pkg.getSplitNames();
+ info.versionCode = ((ParsingPackageHidden) pkg).getVersionCode();
+ info.versionCodeMajor = ((ParsingPackageHidden) pkg).getVersionCodeMajor();
+ info.baseRevisionCode = pkg.getBaseRevisionCode();
+ info.splitRevisionCodes = pkg.getSplitRevisionCodes();
+ info.versionName = pkg.getVersionName();
+ info.sharedUserId = pkg.getSharedUserId();
+ info.sharedUserLabel = pkg.getSharedUserLabelResourceId();
+ info.applicationInfo = applicationInfo;
+ info.installLocation = pkg.getInstallLocation();
+ if ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
+ || (info.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
+ info.requiredForAllUsers = pkg.isRequiredForAllUsers();
+ }
+ info.restrictedAccountType = pkg.getRestrictedAccountType();
+ info.requiredAccountType = pkg.getRequiredAccountType();
+ info.overlayTarget = pkg.getOverlayTarget();
+ info.targetOverlayableName = pkg.getOverlayTargetOverlayableName();
+ info.overlayCategory = pkg.getOverlayCategory();
+ info.overlayPriority = pkg.getOverlayPriority();
+ info.mOverlayIsStatic = pkg.isOverlayIsStatic();
+ info.compileSdkVersion = pkg.getCompileSdkVersion();
+ info.compileSdkVersionCodename = pkg.getCompileSdkVersionCodeName();
+ info.isStub = pkg.isStub();
+ info.coreApp = pkg.isCoreApp();
+ info.isApex = pkg.isApex();
+
+ if ((flags & PackageManager.GET_CONFIGURATIONS) != 0) {
+ int size = pkg.getConfigPreferences().size();
+ if (size > 0) {
+ info.configPreferences = new ConfigurationInfo[size];
+ pkg.getConfigPreferences().toArray(info.configPreferences);
+ }
+ size = pkg.getRequestedFeatures().size();
+ if (size > 0) {
+ info.reqFeatures = new FeatureInfo[size];
+ pkg.getRequestedFeatures().toArray(info.reqFeatures);
+ }
+ size = pkg.getFeatureGroups().size();
+ if (size > 0) {
+ info.featureGroups = new FeatureGroupInfo[size];
+ pkg.getFeatureGroups().toArray(info.featureGroups);
+ }
+ }
+ if ((flags & PackageManager.GET_PERMISSIONS) != 0) {
+ int size = ArrayUtils.size(pkg.getPermissions());
+ if (size > 0) {
+ info.permissions = new PermissionInfo[size];
+ for (int i = 0; i < size; i++) {
+ final var permission = pkg.getPermissions().get(i);
+ final var permissionInfo = generatePermissionInfo(permission, flags);
+ info.permissions[i] = permissionInfo;
+ }
+ }
+ final List<ParsedUsesPermission> usesPermissions = pkg.getUsesPermissions();
+ size = usesPermissions.size();
+ if (size > 0) {
+ info.requestedPermissions = new String[size];
+ info.requestedPermissionsFlags = new int[size];
+ for (int i = 0; i < size; i++) {
+ final ParsedUsesPermission usesPermission = usesPermissions.get(i);
+ info.requestedPermissions[i] = usesPermission.getName();
+ // The notion of required permissions is deprecated but for compatibility.
+ info.requestedPermissionsFlags[i] |=
+ PackageInfo.REQUESTED_PERMISSION_REQUIRED;
+ if ((usesPermission.getUsesPermissionFlags()
+ & ParsedUsesPermission.FLAG_NEVER_FOR_LOCATION) != 0) {
+ info.requestedPermissionsFlags[i] |=
+ PackageInfo.REQUESTED_PERMISSION_NEVER_FOR_LOCATION;
+ }
+ if (pkg.getImplicitPermissions().contains(info.requestedPermissions[i])) {
+ info.requestedPermissionsFlags[i] |=
+ PackageInfo.REQUESTED_PERMISSION_IMPLICIT;
+ }
+ }
+ }
+ }
+ if ((flags & PackageManager.GET_ATTRIBUTIONS_LONG) != 0) {
+ int size = ArrayUtils.size(pkg.getAttributions());
+ if (size > 0) {
+ info.attributions = new Attribution[size];
+ for (int i = 0; i < size; i++) {
+ ParsedAttribution parsedAttribution = pkg.getAttributions().get(i);
+ if (parsedAttribution != null) {
+ info.attributions[i] = new Attribution(parsedAttribution.getTag(),
+ parsedAttribution.getLabel());
+ }
+ }
+ }
+ if (pkg.isAttributionsUserVisible()) {
+ info.applicationInfo.privateFlagsExt
+ |= ApplicationInfo.PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE;
+ } else {
+ info.applicationInfo.privateFlagsExt
+ &= ~ApplicationInfo.PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE;
+ }
+ } else {
+ info.applicationInfo.privateFlagsExt
+ &= ~ApplicationInfo.PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE;
+ }
+
+ final SigningDetails signingDetails = pkg.getSigningDetails();
+ // deprecated method of getting signing certificates
+ if ((flags & PackageManager.GET_SIGNATURES) != 0) {
+ if (signingDetails.hasPastSigningCertificates()) {
+ // Package has included signing certificate rotation information. Return the oldest
+ // cert so that programmatic checks keep working even if unaware of key rotation.
+ info.signatures = new Signature[1];
+ info.signatures[0] = signingDetails.getPastSigningCertificates()[0];
+ } else if (signingDetails.hasSignatures()) {
+ // otherwise keep old behavior
+ int numberOfSigs = signingDetails.getSignatures().length;
+ info.signatures = new Signature[numberOfSigs];
+ System.arraycopy(signingDetails.getSignatures(), 0, info.signatures, 0,
+ numberOfSigs);
+ }
+ }
+
+ // replacement for GET_SIGNATURES
+ if ((flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0) {
+ if (signingDetails != SigningDetails.UNKNOWN) {
+ // only return a valid SigningInfo if there is signing information to report
+ info.signingInfo = new SigningInfo(signingDetails);
+ } else {
+ info.signingInfo = null;
+ }
+ }
+
+ if ((flags & PackageManager.GET_ACTIVITIES) != 0) {
+ final int size = pkg.getActivities().size();
+ if (size > 0) {
+ int num = 0;
+ final ActivityInfo[] res = new ActivityInfo[size];
+ for (int i = 0; i < size; i++) {
+ final ParsedActivity a = pkg.getActivities().get(i);
+ if (isMatch(pkg, a.isDirectBootAware(), flags)) {
+ if (PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals(
+ a.getName())) {
+ continue;
+ }
+ res[num++] = generateActivityInfo(a, flags, applicationInfo);
+ }
+ }
+ info.activities = ArrayUtils.trimToSize(res, num);
+ }
+ }
+ if ((flags & PackageManager.GET_RECEIVERS) != 0) {
+ final int size = pkg.getReceivers().size();
+ if (size > 0) {
+ int num = 0;
+ final ActivityInfo[] res = new ActivityInfo[size];
+ for (int i = 0; i < size; i++) {
+ final ParsedActivity a = pkg.getReceivers().get(i);
+ if (isMatch(pkg, a.isDirectBootAware(), flags)) {
+ res[num++] = generateActivityInfo(a, flags, applicationInfo);
+ }
+ }
+ info.receivers = ArrayUtils.trimToSize(res, num);
+ }
+ }
+ if ((flags & PackageManager.GET_SERVICES) != 0) {
+ final int size = pkg.getServices().size();
+ if (size > 0) {
+ int num = 0;
+ final ServiceInfo[] res = new ServiceInfo[size];
+ for (int i = 0; i < size; i++) {
+ final ParsedService s = pkg.getServices().get(i);
+ if (isMatch(pkg, s.isDirectBootAware(), flags)) {
+ res[num++] = generateServiceInfo(s, flags, applicationInfo);
+ }
+ }
+ info.services = ArrayUtils.trimToSize(res, num);
+ }
+ }
+ if ((flags & PackageManager.GET_PROVIDERS) != 0) {
+ final int size = pkg.getProviders().size();
+ if (size > 0) {
+ int num = 0;
+ final ProviderInfo[] res = new ProviderInfo[size];
+ for (int i = 0; i < size; i++) {
+ final ParsedProvider pr = pkg.getProviders().get(i);
+ if (isMatch(pkg, pr.isDirectBootAware(), flags)) {
+ res[num++] = generateProviderInfo(pkg, pr, flags, applicationInfo, userId);
+ }
+ }
+ info.providers = ArrayUtils.trimToSize(res, num);
+ }
+ }
+ if ((flags & PackageManager.GET_INSTRUMENTATION) != 0) {
+ final int size = pkg.getInstrumentations().size();
+ if (size > 0) {
+ info.instrumentation = new InstrumentationInfo[size];
+ for (int i = 0; i < size; i++) {
+ info.instrumentation[i] = generateInstrumentationInfo(
+ pkg.getInstrumentations().get(i), pkg, flags, userId);
+ }
+ }
+ }
+
+ return info;
+ }
+
+ private static void updateApplicationInfo(ApplicationInfo ai, long flags) {
+ if ((flags & PackageManager.GET_META_DATA) == 0) {
+ ai.metaData = null;
+ }
+ if ((flags & PackageManager.GET_SHARED_LIBRARY_FILES) == 0) {
+ ai.sharedLibraryFiles = null;
+ ai.sharedLibraryInfos = null;
+ }
+
+ // CompatibilityMode is global state.
+ if (!ParsingPackageUtils.sCompatibilityModeEnabled) {
+ ai.disableCompatibilityMode();
+ }
+
+ if (ai.category == ApplicationInfo.CATEGORY_UNDEFINED) {
+ ai.category = FallbackCategoryProvider.getFallbackCategory(ai.packageName);
+ }
+ ai.seInfoUser = COMPLETE_STR;
+ }
+
+ @Nullable
+ private static ApplicationInfo generateApplicationInfo(@NonNull AndroidPackage pkg,
+ @PackageManager.ApplicationInfoFlagsBits long flags, @UserIdInt int userId) {
+
+ // Make shallow copy so we can store the metadata/libraries safely
+ ApplicationInfo info = ((AndroidPackageHidden) pkg).toAppInfoWithoutState();
+
+ updateApplicationInfo(info, flags);
+
+ initForUser(info, pkg, userId);
+
+ info.primaryCpuAbi = AndroidPackageLegacyUtils.getRawPrimaryCpuAbi(pkg);
+ info.secondaryCpuAbi = AndroidPackageLegacyUtils.getRawSecondaryCpuAbi(pkg);
+
+ if ((flags & PackageManager.GET_META_DATA) != 0) {
+ info.metaData = pkg.getMetaData();
+ }
+ if ((flags & PackageManager.GET_SHARED_LIBRARY_FILES) != 0) {
+ List<String> usesLibraryFiles = pkg.getUsesLibraries();
+
+ info.sharedLibraryFiles = usesLibraryFiles.isEmpty()
+ ? null : usesLibraryFiles.toArray(new String[0]);
+ }
+
+ return info;
+ }
+
+ @Nullable
+ private static ActivityInfo generateActivityInfo(ParsedActivity a,
+ @PackageManager.ComponentInfoFlagsBits long flags,
+ @NonNull ApplicationInfo applicationInfo) {
+ if (a == null) return null;
+
+ // Make shallow copies so we can store the metadata safely
+ ActivityInfo ai = new ActivityInfo();
+ ai.targetActivity = a.getTargetActivity();
+ ai.processName = a.getProcessName();
+ ai.exported = a.isExported();
+ ai.theme = a.getTheme();
+ ai.uiOptions = a.getUiOptions();
+ ai.parentActivityName = a.getParentActivityName();
+ ai.permission = a.getPermission();
+ ai.taskAffinity = a.getTaskAffinity();
+ ai.flags = a.getFlags();
+ ai.privateFlags = a.getPrivateFlags();
+ ai.launchMode = a.getLaunchMode();
+ ai.documentLaunchMode = a.getDocumentLaunchMode();
+ ai.maxRecents = a.getMaxRecents();
+ ai.configChanges = a.getConfigChanges();
+ ai.softInputMode = a.getSoftInputMode();
+ ai.persistableMode = a.getPersistableMode();
+ ai.lockTaskLaunchMode = a.getLockTaskLaunchMode();
+ ai.screenOrientation = a.getScreenOrientation();
+ ai.resizeMode = a.getResizeMode();
+ ai.setMaxAspectRatio(a.getMaxAspectRatio());
+ ai.setMinAspectRatio(a.getMinAspectRatio());
+ ai.supportsSizeChanges = a.isSupportsSizeChanges();
+ ai.requestedVrComponent = a.getRequestedVrComponent();
+ ai.rotationAnimation = a.getRotationAnimation();
+ ai.colorMode = a.getColorMode();
+ ai.windowLayout = a.getWindowLayout();
+ ai.attributionTags = a.getAttributionTags();
+ if ((flags & PackageManager.GET_META_DATA) != 0) {
+ var metaData = a.getMetaData();
+ // Backwards compatibility, coerce to null if empty
+ ai.metaData = metaData.isEmpty() ? null : metaData;
+ } else {
+ ai.metaData = null;
+ }
+ ai.applicationInfo = applicationInfo;
+ ai.requiredDisplayCategory = a.getRequiredDisplayCategory();
+ ai.setKnownActivityEmbeddingCerts(a.getKnownActivityEmbeddingCerts());
+ assignFieldsComponentInfoParsedMainComponent(ai, a);
+ return ai;
+ }
+
+ @Nullable
+ private static ServiceInfo generateServiceInfo(ParsedService s,
+ @PackageManager.ComponentInfoFlagsBits long flags,
+ @NonNull ApplicationInfo applicationInfo) {
+ if (s == null) return null;
+
+ // Make shallow copies so we can store the metadata safely
+ ServiceInfo si = new ServiceInfo();
+ si.exported = s.isExported();
+ si.flags = s.getFlags();
+ si.permission = s.getPermission();
+ si.processName = s.getProcessName();
+ si.mForegroundServiceType = s.getForegroundServiceType();
+ si.applicationInfo = applicationInfo;
+ if ((flags & PackageManager.GET_META_DATA) != 0) {
+ var metaData = s.getMetaData();
+ // Backwards compatibility, coerce to null if empty
+ si.metaData = metaData.isEmpty() ? null : metaData;
+ }
+ assignFieldsComponentInfoParsedMainComponent(si, s);
+ return si;
+ }
+
+ @Nullable
+ private static ProviderInfo generateProviderInfo(AndroidPackage pkg, ParsedProvider p,
+ @PackageManager.ComponentInfoFlagsBits long flags,
+ @NonNull ApplicationInfo applicationInfo, int userId) {
+ if (p == null) return null;
+
+ if (!pkg.getPackageName().equals(applicationInfo.packageName)) {
+ Slog.wtf(TAG, "AppInfo's package name is different. Expected=" + pkg.getPackageName()
+ + " actual=" + applicationInfo.packageName);
+ applicationInfo = generateApplicationInfo(pkg, flags, userId);
+ }
+
+ // Make shallow copies so we can store the metadata safely
+ ProviderInfo pi = new ProviderInfo();
+ pi.exported = p.isExported();
+ pi.flags = p.getFlags();
+ pi.processName = p.getProcessName();
+ pi.authority = p.getAuthority();
+ pi.isSyncable = p.isSyncable();
+ pi.readPermission = p.getReadPermission();
+ pi.writePermission = p.getWritePermission();
+ pi.grantUriPermissions = p.isGrantUriPermissions();
+ pi.forceUriPermissions = p.isForceUriPermissions();
+ pi.multiprocess = p.isMultiProcess();
+ pi.initOrder = p.getInitOrder();
+ pi.uriPermissionPatterns = p.getUriPermissionPatterns().toArray(new PatternMatcher[0]);
+ pi.pathPermissions = p.getPathPermissions().toArray(new PathPermission[0]);
+ if ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) == 0) {
+ pi.uriPermissionPatterns = null;
+ }
+ if ((flags & PackageManager.GET_META_DATA) != 0) {
+ var metaData = p.getMetaData();
+ // Backwards compatibility, coerce to null if empty
+ pi.metaData = metaData.isEmpty() ? null : metaData;
+ }
+ pi.applicationInfo = applicationInfo;
+ assignFieldsComponentInfoParsedMainComponent(pi, p);
+ return pi;
+ }
+
+ @Nullable
+ private static InstrumentationInfo generateInstrumentationInfo(ParsedInstrumentation i,
+ AndroidPackage pkg, @PackageManager.ComponentInfoFlagsBits long flags, int userId) {
+ if (i == null) return null;
+
+ InstrumentationInfo info = new InstrumentationInfo();
+ info.targetPackage = i.getTargetPackage();
+ info.targetProcesses = i.getTargetProcesses();
+ info.handleProfiling = i.isHandleProfiling();
+ info.functionalTest = i.isFunctionalTest();
+
+ info.sourceDir = pkg.getBaseApkPath();
+ info.publicSourceDir = pkg.getBaseApkPath();
+ info.splitNames = pkg.getSplitNames();
+ info.splitSourceDirs = pkg.getSplitCodePaths().length == 0 ? null : pkg.getSplitCodePaths();
+ info.splitPublicSourceDirs = pkg.getSplitCodePaths().length == 0
+ ? null : pkg.getSplitCodePaths();
+ info.splitDependencies = pkg.getSplitDependencies().size() == 0
+ ? null : pkg.getSplitDependencies();
+
+ initForUser(info, pkg, userId);
+
+ info.primaryCpuAbi = AndroidPackageLegacyUtils.getRawPrimaryCpuAbi(pkg);
+ info.secondaryCpuAbi = AndroidPackageLegacyUtils.getRawSecondaryCpuAbi(pkg);
+ info.nativeLibraryDir = pkg.getNativeLibraryDir();
+ info.secondaryNativeLibraryDir = pkg.getSecondaryNativeLibraryDir();
+
+ assignFieldsPackageItemInfoParsedComponent(info, i);
+
+ if ((flags & PackageManager.GET_META_DATA) == 0) {
+ info.metaData = null;
+ } else {
+ var metaData = i.getMetaData();
+ // Backwards compatibility, coerce to null if empty
+ info.metaData = metaData.isEmpty() ? null : metaData;
+ }
+
+ return info;
+ }
+
+ @Nullable
+ private static PermissionInfo generatePermissionInfo(ParsedPermission p,
+ @PackageManager.ComponentInfoFlagsBits long flags) {
+ // TODO(b/135203078): Remove null checks and make all usages @NonNull
+ if (p == null) return null;
+
+ PermissionInfo pi = new PermissionInfo(p.getBackgroundPermission());
+
+ assignFieldsPackageItemInfoParsedComponent(pi, p);
+
+ pi.group = p.getGroup();
+ pi.requestRes = p.getRequestRes();
+ pi.protectionLevel = p.getProtectionLevel();
+ pi.descriptionRes = p.getDescriptionRes();
+ pi.flags = p.getFlags();
+ pi.knownCerts = p.getKnownCerts();
+
+ if ((flags & PackageManager.GET_META_DATA) == 0) {
+ pi.metaData = null;
+ } else {
+ var metaData = p.getMetaData();
+ // Backwards compatibility, coerce to null if empty
+ pi.metaData = metaData.isEmpty() ? null : metaData;
+ }
+ return pi;
+ }
+
+ private static void assignFieldsComponentInfoParsedMainComponent(
+ @NonNull ComponentInfo info, @NonNull ParsedMainComponent component) {
+ assignFieldsPackageItemInfoParsedComponent(info, component);
+ info.descriptionRes = component.getDescriptionRes();
+ info.directBootAware = component.isDirectBootAware();
+ info.enabled = component.isEnabled();
+ info.splitName = component.getSplitName();
+ info.attributionTags = component.getAttributionTags();
+ info.nonLocalizedLabel = component.getNonLocalizedLabel();
+ info.icon = component.getIcon();
+ }
+
+ private static void assignFieldsPackageItemInfoParsedComponent(
+ @NonNull PackageItemInfo packageItemInfo, @NonNull ParsedComponent component) {
+ packageItemInfo.nonLocalizedLabel = ComponentParseUtils.getNonLocalizedLabel(component);
+ packageItemInfo.icon = ComponentParseUtils.getIcon(component);
+ packageItemInfo.banner = component.getBanner();
+ packageItemInfo.labelRes = component.getLabelRes();
+ packageItemInfo.logo = component.getLogo();
+ packageItemInfo.name = component.getName();
+ packageItemInfo.packageName = component.getPackageName();
+ }
+
+ private static void initForUser(ApplicationInfo output, AndroidPackage input,
+ @UserIdInt int userId) {
+ PackageImpl pkg = ((PackageImpl) input);
+ String packageName = input.getPackageName();
+ output.uid = UserHandle.getUid(userId, UserHandle.getAppId(input.getUid()));
+
+ // For performance reasons, all these paths are built as strings
+ final String credentialDir = pkg.getBaseAppDataCredentialProtectedDirForSystemUser();
+ final String deviceDir = pkg.getBaseAppDataDeviceProtectedDirForSystemUser();
+ if (credentialDir != null && deviceDir != null) {
+ if (userId == UserHandle.USER_SYSTEM) {
+ output.credentialProtectedDataDir = credentialDir + packageName;
+ output.deviceProtectedDataDir = deviceDir + packageName;
+ } else {
+ // Convert /data/user/0/ -> /data/user/1/com.example.app
+ String userIdString = String.valueOf(userId);
+ int credentialLength = credentialDir.length();
+ output.credentialProtectedDataDir = new StringBuilder(credentialDir)
+ .replace(credentialLength - 2, credentialLength - 1, userIdString)
+ .append(packageName)
+ .toString();
+ int deviceLength = deviceDir.length();
+ output.deviceProtectedDataDir = new StringBuilder(deviceDir)
+ .replace(deviceLength - 2, deviceLength - 1, userIdString)
+ .append(packageName)
+ .toString();
+ }
+ }
+
+ if (input.isDefaultToDeviceProtectedStorage()
+ && PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) {
+ output.dataDir = output.deviceProtectedDataDir;
+ } else {
+ output.dataDir = output.credentialProtectedDataDir;
+ }
+ }
+
+ // This duplicates the ApplicationInfo variant because it uses field assignment and the classes
+ // don't inherit from each other, unfortunately. Consolidating logic would introduce overhead.
+ private static void initForUser(InstrumentationInfo output, AndroidPackage input,
+ @UserIdInt int userId) {
+ PackageImpl pkg = ((PackageImpl) input);
+ String packageName = input.getPackageName();
+
+ // For performance reasons, all these paths are built as strings
+ final String credentialDir = pkg.getBaseAppDataCredentialProtectedDirForSystemUser();
+ final String deviceDir = pkg.getBaseAppDataDeviceProtectedDirForSystemUser();
+ if (credentialDir != null && deviceDir != null) {
+ if (userId == UserHandle.USER_SYSTEM) {
+ output.credentialProtectedDataDir = credentialDir + packageName;
+ output.deviceProtectedDataDir = deviceDir + packageName;
+ } else {
+ // Convert /data/user/0/ -> /data/user/1/com.example.app
+ String userIdString = String.valueOf(userId);
+ int credentialLength = credentialDir.length();
+ output.credentialProtectedDataDir = new StringBuilder(credentialDir)
+ .replace(credentialLength - 2, credentialLength - 1, userIdString)
+ .append(packageName)
+ .toString();
+ int deviceLength = deviceDir.length();
+ output.deviceProtectedDataDir = new StringBuilder(deviceDir)
+ .replace(deviceLength - 2, deviceLength - 1, userIdString)
+ .append(packageName)
+ .toString();
+ }
+ }
+
+ if (input.isDefaultToDeviceProtectedStorage()
+ && PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) {
+ output.dataDir = output.deviceProtectedDataDir;
+ } else {
+ output.dataDir = output.credentialProtectedDataDir;
+ }
+ }
+
+ /**
+ * Test if the given component is considered system, enabled and a match for the given
+ * flags.
+ *
+ * <p>
+ * Expects at least one of {@link PackageManager#MATCH_DIRECT_BOOT_AWARE} and {@link
+ * PackageManager#MATCH_DIRECT_BOOT_UNAWARE} are specified in {@code flags}.
+ * </p>
+ */
+ private static boolean isMatch(AndroidPackage pkg,
+ boolean isComponentDirectBootAware, long flags) {
+ final boolean isSystem = ((AndroidPackageHidden) pkg).isSystem();
+ if ((flags & PackageManager.MATCH_SYSTEM_ONLY) != 0) {
+ if (!isSystem) {
+ return reportIfDebug(false, flags);
+ }
+ }
+
+ final boolean matchesUnaware = ((flags & PackageManager.MATCH_DIRECT_BOOT_UNAWARE) != 0)
+ && !isComponentDirectBootAware;
+ final boolean matchesAware = ((flags & PackageManager.MATCH_DIRECT_BOOT_AWARE) != 0)
+ && isComponentDirectBootAware;
+ return reportIfDebug(matchesUnaware || matchesAware, flags);
+ }
+
+ private static boolean reportIfDebug(boolean result, long flags) {
+ if (DEBUG && !result) {
+ Slog.i(TAG, "No match!; flags: "
+ + DebugUtils.flagsToString(PackageManager.class, "MATCH_", flags) + " "
+ + Debug.getCaller());
+ }
+ return result;
+ }
+}
diff --git a/core/java/com/android/internal/pm/parsing/PackageParser2.java b/core/java/com/android/internal/pm/parsing/PackageParser2.java
index e41329340000..2c546728d712 100644
--- a/core/java/com/android/internal/pm/parsing/PackageParser2.java
+++ b/core/java/com/android/internal/pm/parsing/PackageParser2.java
@@ -20,6 +20,7 @@ import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityThread;
+import android.app.Application;
import android.content.pm.ApplicationInfo;
import android.content.pm.parsing.PackageLite;
import android.content.pm.parsing.result.ParseInput;
@@ -40,6 +41,7 @@ import com.android.internal.pm.pkg.parsing.ParsingUtils;
import com.android.internal.util.ArrayUtils;
import java.io.File;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -78,10 +80,19 @@ public class PackageParser2 implements AutoCloseable {
displayMetrics.setToDefaults();
}
- PermissionManager permissionManager = ActivityThread.currentApplication()
- .getSystemService(PermissionManager.class);
- List<PermissionManager.SplitPermissionInfo> splitPermissions = permissionManager
- .getSplitPermissions();
+ List<PermissionManager.SplitPermissionInfo> splitPermissions = null;
+
+ final Application application = ActivityThread.currentApplication();
+ if (application != null) {
+ final PermissionManager permissionManager =
+ application.getSystemService(PermissionManager.class);
+ if (permissionManager != null) {
+ splitPermissions = permissionManager.getSplitPermissions();
+ }
+ }
+ if (splitPermissions == null) {
+ splitPermissions = new ArrayList<>();
+ }
mCacher = cacher;
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index 9c883d18a9ae..56ea52d2ad8b 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -225,6 +225,19 @@ nativeGetSensorAtIndex(JNIEnv *env, jclass clazz, jlong sensorManager, jobject s
return translateNativeSensorToJavaSensor(env, sensor, *sensorList[index]) != NULL;
}
+static jboolean nativeGetDefaultDeviceSensorAtIndex(JNIEnv *env, jclass clazz, jlong sensorManager,
+ jobject sensor, jint index) {
+ SensorManager *mgr = reinterpret_cast<SensorManager *>(sensorManager);
+
+ Vector<Sensor> sensorList;
+ ssize_t count = mgr->getDefaultDeviceSensorList(sensorList);
+ if (ssize_t(index) >= count) {
+ return false;
+ }
+
+ return translateNativeSensorToJavaSensor(env, sensor, sensorList[index]) != NULL;
+}
+
static void
nativeGetDynamicSensors(JNIEnv *env, jclass clazz, jlong sensorManager, jobject sensorList) {
@@ -539,6 +552,9 @@ static const JNINativeMethod gSystemSensorManagerMethods[] = {
{"nativeGetSensorAtIndex", "(JLandroid/hardware/Sensor;I)Z",
(void *)nativeGetSensorAtIndex},
+ {"nativeGetDefaultDeviceSensorAtIndex", "(JLandroid/hardware/Sensor;I)Z",
+ (void *)nativeGetDefaultDeviceSensorAtIndex},
+
{"nativeGetDynamicSensors", "(JLjava/util/List;)V", (void *)nativeGetDynamicSensors},
{"nativeGetRuntimeSensors", "(JILjava/util/List;)V", (void *)nativeGetRuntimeSensors},
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index 052e2f20a313..d3f3af738273 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -610,6 +610,9 @@ message GlobalSettingsProto {
// ringer mode.
optional SettingProto mode_ringer = 75 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ // This is an optional feature where ringer mode affects alarm stream as well
+ optional SettingProto mute_alarm_stream_with_ringer_mode = 160 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
reserved 147; // Used to be apply_ramping_ringer
message MultiSim {
@@ -1086,5 +1089,5 @@ message GlobalSettingsProto {
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 160;
+ // Next tag = 161;
}
diff --git a/core/proto/android/view/insetssource.proto b/core/proto/android/view/insetssource.proto
index e6c6d5978779..118dfc8165d2 100644
--- a/core/proto/android/view/insetssource.proto
+++ b/core/proto/android/view/insetssource.proto
@@ -26,7 +26,7 @@ option java_multiple_files = true;
* Represents a {@link android.view.InsetsSource} object.
*/
message InsetsSourceProto {
- optional string type = 1;
+ optional string type = 1 [deprecated=true];
optional .android.graphics.RectProto frame = 2;
optional .android.graphics.RectProto visible_frame = 3;
optional bool visible = 4;
diff --git a/core/proto/android/view/insetssourceconsumer.proto b/core/proto/android/view/insetssourceconsumer.proto
index a01ad8eea582..882163f6a9d4 100644
--- a/core/proto/android/view/insetssourceconsumer.proto
+++ b/core/proto/android/view/insetssourceconsumer.proto
@@ -27,11 +27,12 @@ option java_multiple_files = true;
* Represents a {@link android.view.InsetsSourceConsumer} object.
*/
message InsetsSourceConsumerProto {
- optional string internal_insets_type = 1;
+ optional string internal_insets_type = 1 [deprecated=true];
optional bool has_window_focus = 2;
optional bool is_requested_visible = 3;
optional InsetsSourceControlProto source_control = 4;
optional .android.graphics.RectProto pending_frame = 5;
optional .android.graphics.RectProto pending_visible_frame = 6;
optional int32 animation_state = 7;
+ optional int32 type_number = 8;
} \ No newline at end of file
diff --git a/core/proto/android/view/insetssourcecontrol.proto b/core/proto/android/view/insetssourcecontrol.proto
index 3ac3cbfafaff..afab57f416cf 100644
--- a/core/proto/android/view/insetssourcecontrol.proto
+++ b/core/proto/android/view/insetssourcecontrol.proto
@@ -27,7 +27,8 @@ option java_multiple_files = true;
* Represents a {@link android.view.InsetsSourceControl} object.
*/
message InsetsSourceControlProto {
- optional string type = 1;
+ optional string type = 1 [deprecated=true];
optional .android.graphics.PointProto position = 2;
optional SurfaceControlProto leash = 3;
+ optional int32 type_number = 4;
} \ No newline at end of file
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 596cfe5a695d..d1143c43ad61 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2986,7 +2986,12 @@
depends on the number of isolated services that an application starts,
and how much memory those services save by preloading and sharing memory with
the app zygote. Therefore, it is recommended to measure memory usage under
- typical workloads to determine whether it makes sense to use this flag. -->
+ typical workloads to determine whether it makes sense to use this flag.
+
+ <p>There is a limit to the number of isolated services that can be spawned from
+ the Application Zygote; the absolute limit is 100, but due to potential
+ delays in service process cleanup, a much safer limit to use in practice is 50.
+ -->
<attr name="useAppZygote" format="boolean" />
<!-- If this is a foreground service, specify its category. -->
<attr name="foregroundServiceType" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 5400c5848f02..804e9ef8476b 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2262,6 +2262,9 @@
<!-- The default min volume for the alarm stream -->
<integer name="config_audio_alarm_min_vol">1</integer>
+ <!-- Flag indicating if ringer mode affects alarm stream -->
+ <bool name="config_audio_ringer_mode_affects_alarm_stream">false</bool>
+
<!-- The default value for whether head tracking for
spatial audio is enabled for a newly connected audio device -->
<bool name="config_spatial_audio_head_tracking_enabled_default">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index eeef19291c4f..9589fb00fd5d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -288,6 +288,7 @@
<java-symbol type="integer" name="config_audio_ring_vol_default" />
<java-symbol type="integer" name="config_audio_ring_vol_steps" />
<java-symbol type="integer" name="config_audio_alarm_min_vol" />
+ <java-symbol type="bool" name="config_audio_ringer_mode_affects_alarm_stream" />
<java-symbol type="bool" name="config_spatial_audio_head_tracking_enabled_default" />
<java-symbol type="bool" name="config_avoidGfxAccel" />
<java-symbol type="bool" name="config_bluetooth_address_validation" />
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
index 930b1a4dde26..95d50499b92f 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
@@ -65,7 +65,7 @@ public class ClientTransactionListenerControllerTest {
mHandler = getInstrumentation().getContext().getMainThreadHandler();
mController = spy(ClientTransactionListenerController.createInstanceForTesting(
mDisplayManager));
- doReturn(true).when(mController).isSyncWindowConfigUpdateFlagEnabled();
+ doReturn(true).when(mController).isBundleClientTransactionFlagEnabled();
}
@Test
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
index 8308e7c5ef19..1617eda6a77c 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
@@ -20,12 +20,15 @@ import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresFlagsEnabled
import android.platform.test.flag.junit.CheckFlagsRule
import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import android.util.SparseArray
import androidx.core.util.forEach
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
+import org.junit.After
+import org.junit.Before
import org.junit.Rule
import kotlin.math.ceil
import kotlin.math.floor
@@ -45,6 +48,19 @@ class FontScaleConverterFactoryTest {
@get:Rule
val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+ private lateinit var defaultLookupTables: SparseArray<FontScaleConverter>
+
+ @Before
+ fun setup() {
+ defaultLookupTables = FontScaleConverterFactory.sLookupTables.clone()
+ }
+
+ @After
+ fun teardown() {
+ // Restore the default tables (since some tests will have added extras to the cache)
+ FontScaleConverterFactory.sLookupTables = defaultLookupTables
+ }
+
@Test
fun scale200IsTwiceAtSmallSizes() {
val table = FontScaleConverterFactory.forScale(2F)!!
@@ -245,7 +261,7 @@ class FontScaleConverterFactoryTest {
}
companion object {
- private const val CONVERSION_TOLERANCE = 0.05f
+ private const val CONVERSION_TOLERANCE = 0.18f
}
}
diff --git a/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java b/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java
index a5bbeb58bc08..9292f667b27c 100644
--- a/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java
+++ b/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java
@@ -16,7 +16,6 @@
package android.window.flags;
-import static com.android.window.flags.Flags.syncWindowConfigUpdateFlag;
import static com.android.window.flags.Flags.taskFragmentSystemOrganizerFlag;
import android.platform.test.annotations.Presubmit;
@@ -39,12 +38,6 @@ import org.junit.runner.RunWith;
public class WindowFlagsTest {
@Test
- public void testSyncWindowConfigUpdateFlag() {
- // No crash when accessing the flag.
- syncWindowConfigUpdateFlag();
- }
-
- @Test
public void testTaskFragmentSystemOrganizerFlag() {
// No crash when accessing the flag.
taskFragmentSystemOrganizerFlag();
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index b6813ffba312..b209c7c261ba 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -30,6 +30,7 @@ import static com.android.internal.app.MatcherUtils.first;
import static com.android.internal.app.ResolverDataProvider.createPackageManagerMockedInfo;
import static com.android.internal.app.ResolverWrapperActivity.sOverrides;
+import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
import static org.hamcrest.CoreMatchers.allOf;
@@ -46,6 +47,7 @@ import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.text.TextUtils;
import android.view.View;
import android.widget.RelativeLayout;
@@ -88,7 +90,8 @@ public class ResolverActivityTest {
public ActivityTestRule<ResolverWrapperActivity> mActivityRule =
new ActivityTestRule<>(ResolverWrapperActivity.class, false,
false);
-
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void cleanOverrideData() {
sOverrides.reset();
@@ -1156,6 +1159,97 @@ public class ResolverActivityTest {
sOverrides.cloneProfileUserHandle)));
}
+ @Test
+ public void testTriggerFromPrivateProfile_withoutWorkProfile() throws RemoteException {
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+ markPrivateProfileUserAvailable();
+ Intent sendIntent = createSendImageIntent();
+ List<ResolvedComponentInfo> privateResolvedComponentInfos =
+ createResolvedComponentsForTest(3, sOverrides.privateProfileUserHandle);
+ setupResolverControllers(privateResolvedComponentInfos);
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ onView(withId(R.id.tabs)).check(matches(not(isDisplayed())));
+ assertThat(activity.getPersonalListAdapter().getCount(), is(3));
+ onView(withId(R.id.button_once)).check(matches(not(isEnabled())));
+ onView(withId(R.id.button_always)).check(matches(not(isEnabled())));
+ for (ResolvedComponentInfo resolvedInfo : privateResolvedComponentInfos) {
+ assertEquals(resolvedInfo.getResolveInfoAt(0).userHandle,
+ sOverrides.privateProfileUserHandle);
+ }
+ }
+
+ @Test
+ public void testTriggerFromPrivateProfile_withWorkProfilePresent(){
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+ ResolverActivity.ENABLE_TABBED_VIEW = false;
+ markPrivateProfileUserAvailable();
+ markWorkProfileUserAvailable();
+ Intent sendIntent = createSendImageIntent();
+ List<ResolvedComponentInfo> privateResolvedComponentInfos =
+ createResolvedComponentsForTest(3, sOverrides.privateProfileUserHandle);
+ setupResolverControllers(privateResolvedComponentInfos);
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ assertThat(activity.getPersonalListAdapter().getCount(), is(3));
+ onView(withId(R.id.tabs)).check(matches(not(isDisplayed())));
+ assertEquals(activity.getMultiProfilePagerAdapterCount(), 1);
+ for (ResolvedComponentInfo resolvedInfo : privateResolvedComponentInfos) {
+ assertEquals(resolvedInfo.getResolveInfoAt(0).userHandle,
+ sOverrides.privateProfileUserHandle);
+ }
+ }
+
+ @Test
+ public void testPrivateProfile_triggerFromPrimaryUser_withWorkProfilePresent(){
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+ markPrivateProfileUserAvailable();
+ markWorkProfileUserAvailable();
+ Intent sendIntent = createSendImageIntent();
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(3, PERSONAL_USER_HANDLE);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+ sOverrides.workProfileUserHandle);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ assertThat(activity.getAdapter().getCount(), is(2));
+ assertThat(activity.getWorkListAdapter().getCount(), is(4));
+ onView(withId(R.id.tabs)).check(matches(isDisplayed()));
+ for (ResolvedComponentInfo resolvedInfo : personalResolvedComponentInfos) {
+ assertEquals(resolvedInfo.getResolveInfoAt(0).userHandle,
+ activity.getPersonalProfileUserHandle());
+ }
+ }
+
+ @Test
+ public void testPrivateProfile_triggerFromWorkProfile(){
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+ markPrivateProfileUserAvailable();
+ markWorkProfileUserAvailable();
+ Intent sendIntent = createSendImageIntent();
+
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(3, PERSONAL_USER_HANDLE);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+ sOverrides.workProfileUserHandle);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ assertThat(activity.getAdapter().getCount(), is(2));
+ assertThat(activity.getWorkListAdapter().getCount(), is(4));
+ onView(withId(R.id.tabs)).check(matches(isDisplayed()));
+ for (ResolvedComponentInfo resolvedInfo : personalResolvedComponentInfos) {
+ assertTrue(resolvedInfo.getResolveInfoAt(0).userHandle.equals(
+ activity.getPersonalProfileUserHandle()) || resolvedInfo.getResolveInfoAt(
+ 0).userHandle.equals(activity.getWorkProfileUserHandle()));
+ }
+ }
+
private Intent createSendImageIntent() {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
@@ -1237,6 +1331,10 @@ public class ResolverActivityTest {
ResolverWrapperActivity.sOverrides.cloneProfileUserHandle = UserHandle.of(11);
}
+ private void markPrivateProfileUserAvailable() {
+ ResolverWrapperActivity.sOverrides.privateProfileUserHandle = UserHandle.of(12);
+ }
+
private void setupResolverControllers(
List<ResolvedComponentInfo> personalResolvedComponentInfos,
List<ResolvedComponentInfo> workResolvedComponentInfos) {
@@ -1256,4 +1354,13 @@ public class ResolverActivityTest {
eq(UserHandle.SYSTEM)))
.thenReturn(new ArrayList<>(personalResolvedComponentInfos));
}
+
+ private void setupResolverControllers(
+ List<ResolvedComponentInfo> resolvedComponentInfos) {
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class)))
+ .thenReturn(new ArrayList<>(resolvedComponentInfos));
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
index e193de0a3b20..862cbd5b5e01 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
@@ -88,6 +88,10 @@ public class ResolverWrapperActivity extends ResolverActivity {
return ((ResolverListAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(1));
}
+ int getMultiProfilePagerAdapterCount(){
+ return mMultiProfilePagerAdapter.getCount();
+ }
+
@Override
public boolean isVoiceInteraction() {
if (sOverrides.isVoiceInteraction != null) {
@@ -144,6 +148,11 @@ public class ResolverWrapperActivity extends ResolverActivity {
}
@Override
+ protected UserHandle getPrivateProfileUserHandle() {
+ return sOverrides.privateProfileUserHandle;
+ }
+
+ @Override
protected UserHandle getTabOwnerUserHandleForLaunch() {
if (sOverrides.tabOwnerUserHandleForLaunch == null) {
return super.getTabOwnerUserHandleForLaunch();
@@ -176,6 +185,7 @@ public class ResolverWrapperActivity extends ResolverActivity {
public Boolean isVoiceInteraction;
public UserHandle workProfileUserHandle;
public UserHandle cloneProfileUserHandle;
+ public UserHandle privateProfileUserHandle;
public UserHandle tabOwnerUserHandleForLaunch;
public Integer myUserId;
public boolean hasCrossProfileIntents;
@@ -191,6 +201,7 @@ public class ResolverWrapperActivity extends ResolverActivity {
workResolverListController = mock(ResolverListController.class);
workProfileUserHandle = null;
cloneProfileUserHandle = null;
+ privateProfileUserHandle = null;
tabOwnerUserHandleForLaunch = null;
myUserId = null;
hasCrossProfileIntents = true;
diff --git a/core/tests/overlaytests/Android.mk b/core/tests/overlaytests/Android.mk
deleted file mode 100644
index b798d87878b7..000000000000
--- a/core/tests/overlaytests/Android.mk
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-include $(call all-subdir-makefiles)
diff --git a/core/tests/overlaytests/host/Android.mk b/core/tests/overlaytests/host/Android.mk
deleted file mode 100644
index d58d9393c0b8..000000000000
--- a/core/tests/overlaytests/host/Android.mk
+++ /dev/null
@@ -1,19 +0,0 @@
-# Copyright (C) 2018 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.
-
-LOCAL_PATH := $(call my-dir)
-
-# Include to build test-apps.
-include $(call all-makefiles-under,$(LOCAL_PATH))
-
diff --git a/core/tests/overlaytests/host/test-apps/Android.mk b/core/tests/overlaytests/host/test-apps/Android.mk
deleted file mode 100644
index 5c7187ead31f..000000000000
--- a/core/tests/overlaytests/host/test-apps/Android.mk
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright (C) 2018 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.
-
-include $(call all-subdir-makefiles)
-
diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.bp b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.bp
new file mode 100644
index 000000000000..bb7d63edbebd
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.bp
@@ -0,0 +1,57 @@
+// Copyright (C) 2018 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.
+
+// Error: Cannot get the name of the license module in the
+// ./Android.bp file.
+// If no such license module exists, please add one there first.
+// Then reset the default_applicable_licenses property below with the license module name.
+package {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+ name: "OverlayHostTests_NonPlatformSignatureOverlay",
+ sdk_version: "current",
+ test_suites: ["device-tests"],
+ aaptflags: [
+ "--custom-package com.android.server.om.hosttest.signature_overlay_bad",
+ ],
+}
+
+android_test_helper_app {
+ name: "OverlayHostTests_PlatformSignatureStaticOverlay",
+ sdk_version: "current",
+ test_suites: ["device-tests"],
+ certificate: "platform",
+ manifest: "static/AndroidManifest.xml",
+ aaptflags: [
+ "--custom-package com.android.server.om.hosttest.signature_overlay_static",
+ ],
+}
+
+android_test_helper_app {
+ name: "OverlayHostTests_PlatformSignatureOverlay",
+ sdk_version: "current",
+ test_suites: ["device-tests"],
+ certificate: "platform",
+ aaptflags: [
+ "--custom-package",
+ "com.android.server.om.hosttest.signature_overlay_v1",
+ "--version-code",
+ "1",
+ "--version-name",
+ "v1",
+ ],
+}
diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk
deleted file mode 100644
index b453cde925e4..000000000000
--- a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk
+++ /dev/null
@@ -1,56 +0,0 @@
-# Copyright (C) 2018 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.
-
-LOCAL_PATH := $(call my-dir)
-
-my_package_prefix := com.android.server.om.hosttest.signature_overlay
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_NonPlatformSignatureOverlay
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_bad
-include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_PlatformSignatureStaticOverlay
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := platform
-LOCAL_MANIFEST_FILE := static/AndroidManifest.xml
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_static
-include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_PlatformSignatureOverlay
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := platform
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1
-LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
-include $(BUILD_PACKAGE)
-
-my_package_prefix :=
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.bp b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.bp
new file mode 100644
index 000000000000..ee0c0e526c0a
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.bp
@@ -0,0 +1,97 @@
+// Copyright (C) 2018 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.
+
+// Error: Cannot get the name of the license module in the
+// ./Android.bp file.
+// If no such license module exists, please add one there first.
+// Then reset the default_applicable_licenses property below with the license module name.
+package {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+ name: "OverlayHostTests_UpdateOverlay",
+ srcs: ["src/**/*.java"],
+ sdk_version: "current",
+ test_suites: ["device-tests"],
+ static_libs: ["androidx.test.rules"],
+ aaptflags: ["--no-resource-removal"],
+}
+
+android_test_helper_app {
+ name: "OverlayHostTests_FrameworkOverlayV1",
+ sdk_version: "current",
+ test_suites: ["device-tests"],
+ certificate: "platform",
+ aaptflags: [
+ "--custom-package",
+ "com.android.server.om.hosttest.framework_overlay_v1",
+ "--version-code",
+ "1",
+ "--version-name",
+ "v1",
+ ],
+ resource_dirs: ["framework/v1/res"],
+ manifest: "framework/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+ name: "OverlayHostTests_FrameworkOverlayV2",
+ sdk_version: "current",
+ test_suites: ["device-tests"],
+ certificate: "platform",
+ aaptflags: [
+ "--custom-package",
+ "com.android.server.om.hosttest.framework_overlay_v2",
+ "--version-code",
+ "2",
+ "--version-name",
+ "v2",
+ ],
+ resource_dirs: ["framework/v2/res"],
+ manifest: "framework/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+ name: "OverlayHostTests_AppOverlayV1",
+ sdk_version: "current",
+ test_suites: ["device-tests"],
+ aaptflags: [
+ "--custom-package",
+ "com.android.server.om.hosttest.app_overlay_v1",
+ "--version-code",
+ "1",
+ "--version-name",
+ "v1",
+ ],
+ resource_dirs: ["app/v1/res"],
+ manifest: "app/v1/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+ name: "OverlayHostTests_AppOverlayV2",
+ sdk_version: "current",
+ test_suites: ["device-tests"],
+ aaptflags: [
+ "--custom-package",
+ "com.android.server.om.hosttest.app_overlay_v2",
+ "--version-code",
+ "2",
+ "--version-name",
+ "v2",
+ ],
+ resource_dirs: ["app/v2/res"],
+ manifest: "app/v2/AndroidManifest.xml",
+}
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk
deleted file mode 100644
index 77fc887e9493..000000000000
--- a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk
+++ /dev/null
@@ -1,93 +0,0 @@
-# Copyright (C) 2018 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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
-LOCAL_PACKAGE_NAME := OverlayHostTests_UpdateOverlay
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
-LOCAL_USE_AAPT2 := true
-LOCAL_AAPT_FLAGS := --no-resource-removal
-include $(BUILD_PACKAGE)
-
-my_package_prefix := com.android.server.om.hosttest.framework_overlay
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_FrameworkOverlayV1
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := platform
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1
-LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/framework/v1/res
-LOCAL_MANIFEST_FILE := framework/AndroidManifest.xml
-include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_FrameworkOverlayV2
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := platform
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2
-LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/framework/v2/res
-LOCAL_MANIFEST_FILE := framework/AndroidManifest.xml
-include $(BUILD_PACKAGE)
-
-my_package_prefix := com.android.server.om.hosttest.app_overlay
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_AppOverlayV1
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1
-LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/app/v1/res
-LOCAL_MANIFEST_FILE := app/v1/AndroidManifest.xml
-include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_AppOverlayV2
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../../../../NOTICE
-LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2
-LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/app/v2/res
-LOCAL_MANIFEST_FILE := app/v2/AndroidManifest.xml
-include $(BUILD_PACKAGE)
-
-my_package_prefix :=
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index c4530f64a82d..13d38d2ff2c6 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -347,7 +347,9 @@
<!-- Allow IMS service entitlement app to schedule jobs to run when app in background. -->
<allow-in-power-save-except-idle package="com.android.imsserviceentitlement" />
- <!-- Allow device lock controller app to schedule jobs and alarms when app in background,
- otherwise, it may not be able to enforce provision for managed devices. -->
+ <!-- Allow device lock controller app to schedule jobs and alarms, and have network access
+ when app in background; otherwise, it may not be able to enforce provision for managed
+ devices. -->
<allow-in-power-save package="com.android.devicelockcontroller" />
+ <allow-in-data-usage-save package="com.android.devicelockcontroller" />
</permissions>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index c6f920f55c07..b9efe65d2754 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -575,6 +575,7 @@ applications that come with the platform
<permission name="android.permission.SET_WALLPAPER_COMPONENT"/>
<permission name="android.permission.BIND_WALLPAPER"/>
<permission name="android.permission.CUSTOMIZE_SYSTEM_UI"/>
+ <permission name="android.permission.SET_WALLPAPER_DIM_AMOUNT"/>
</privapp-permissions>
<privapp-permissions package="com.android.dynsystem">
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 da87339b01a7..afd554b6e52b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -360,9 +360,7 @@ class TaskFragmentContainer {
if (activities == null) {
return null;
}
- // Already checked nullity in collectNonFinishingActivities.
- final Rect bounds = getInfo().getConfiguration().windowConfiguration.getBounds();
- return new ActivityStack(activities, isEmpty(), mToken, bounds, mOverlayTag);
+ return new ActivityStack(activities, isEmpty(), mToken, mOverlayTag);
}
/** Adds the activity that will be reparented to this container. */
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
index 85bf2c1e4dca..e4f793c2665b 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
@@ -30,8 +30,8 @@
android:orientation="horizontal"
android:clickable="true"
android:focusable="true"
- android:paddingStart="16dp">
-
+ android:paddingStart="6dp"
+ android:paddingEnd="8dp">
<ImageView
android:id="@+id/application_icon"
android:layout_width="@dimen/desktop_mode_caption_icon_radius"
@@ -43,7 +43,7 @@
android:id="@+id/application_name"
android:layout_width="0dp"
android:layout_height="20dp"
- android:minWidth="80dp"
+ android:maxWidth="86dp"
android:textAppearance="@android:style/TextAppearance.Material.Title"
android:textSize="14sp"
android:textFontWeight="500"
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 8f9de6168bc7..0a40cea3134d 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -413,6 +413,25 @@
<!-- Height of desktop mode caption for fullscreen tasks. -->
<dimen name="desktop_mode_fullscreen_decor_caption_height">36dp</dimen>
+ <!-- Required empty space to be visible for partially offscreen tasks. -->
+ <dimen name="freeform_required_visible_empty_space_in_header">48dp</dimen>
+
+ <!-- Required empty space to be visible for partially offscreen tasks on a smaller screen. -->
+ <dimen name="small_screen_required_visible_empty_space_in_header">12dp</dimen>
+
+ <!-- 32dp width back button + 10dp margin -->
+ <dimen name="caption_left_buttons_width">32dp</dimen>
+
+ <!-- (32 dp buttons + 10dp margins) * 3 buttons-->
+ <dimen name="caption_right_buttons_width">126dp</dimen>
+
+ <!-- 2 buttons * 48dp button size. -->
+ <dimen name="desktop_mode_right_edge_buttons_width">96dp</dimen>
+
+ <!-- 22dp padding + 24dp app icon + 16dp expand button.
+ Text varies in size, we will calculate that width separately. -->
+ <dimen name="desktop_mode_app_details_width_minus_text">62dp</dimen>
+
<!-- The width of the maximize menu in desktop mode. -->
<dimen name="desktop_mode_maximize_menu_width">287dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index 662a5c47d633..a76bd269f31b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -95,7 +95,6 @@ public class BubblePositioner {
private int mMinimumFlyoutWidthLargeScreen;
private PointF mRestingStackPosition;
- private int[] mPaddings = new int[4];
private boolean mShowingInBubbleBar;
private final Point mBubbleBarPosition = new Point();
@@ -344,46 +343,44 @@ public class BubblePositioner {
final int pointerTotalHeight = getPointerSize();
final int expandedViewLargeScreenInsetFurthestEdge =
getExpandedViewLargeScreenInsetFurthestEdge(isOverflow);
+ int[] paddings = new int[4];
if (mDeviceConfig.isLargeScreen()) {
// Note:
// If we're in portrait OR if we're a small tablet, then the two insets values will
// be equal. If we're landscape and a large tablet, the two values will be different.
// [left, top, right, bottom]
- mPaddings[0] = onLeft
+ paddings[0] = onLeft
? mExpandedViewLargeScreenInsetClosestEdge - pointerTotalHeight
: expandedViewLargeScreenInsetFurthestEdge;
- mPaddings[1] = 0;
- mPaddings[2] = onLeft
+ paddings[1] = 0;
+ paddings[2] = onLeft
? expandedViewLargeScreenInsetFurthestEdge
: mExpandedViewLargeScreenInsetClosestEdge - pointerTotalHeight;
// Overflow doesn't show manage button / get padding from it so add padding here
- mPaddings[3] = isOverflow ? mExpandedViewPadding : 0;
- return mPaddings;
+ paddings[3] = isOverflow ? mExpandedViewPadding : 0;
+ return paddings;
} else {
int leftPadding = mInsets.left + mExpandedViewPadding;
int rightPadding = mInsets.right + mExpandedViewPadding;
- final float expandedViewWidth = isOverflow
- ? mOverflowWidth
- : mExpandedViewLargeScreenWidth;
if (showBubblesVertically()) {
if (!onLeft) {
rightPadding += mBubbleSize - pointerTotalHeight;
leftPadding += isOverflow
- ? (mPositionRect.width() - rightPadding - expandedViewWidth)
+ ? (mPositionRect.width() - rightPadding - mOverflowWidth)
: 0;
} else {
leftPadding += mBubbleSize - pointerTotalHeight;
rightPadding += isOverflow
- ? (mPositionRect.width() - leftPadding - expandedViewWidth)
+ ? (mPositionRect.width() - leftPadding - mOverflowWidth)
: 0;
}
}
// [left, top, right, bottom]
- mPaddings[0] = leftPadding;
- mPaddings[1] = showBubblesVertically() ? 0 : mPointerMargin;
- mPaddings[2] = rightPadding;
- mPaddings[3] = 0;
- return mPaddings;
+ paddings[0] = leftPadding;
+ paddings[1] = showBubblesVertically() ? 0 : mPointerMargin;
+ paddings[2] = rightPadding;
+ paddings[3] = 0;
+ return paddings;
}
}
@@ -395,7 +392,7 @@ public class BubblePositioner {
}
/** Gets the y position of the expanded view if it was top-aligned. */
- public float getExpandedViewYTopAligned() {
+ public int getExpandedViewYTopAligned() {
final int top = getAvailableRect().top;
if (showBubblesVertically()) {
return top - mPointerWidth + mExpandedViewPadding;
@@ -413,7 +410,7 @@ public class BubblePositioner {
return getExpandedViewHeightForLargeScreen();
}
// Subtract top insets because availableRect.height would account for that
- int expandedContainerY = (int) getExpandedViewYTopAligned() - getInsets().top;
+ int expandedContainerY = getExpandedViewYTopAligned() - getInsets().top;
int paddingTop = showBubblesVertically()
? 0
: mPointerHeight;
@@ -474,11 +471,11 @@ public class BubblePositioner {
public float getExpandedViewY(BubbleViewProvider bubble, float bubblePosition) {
boolean isOverflow = bubble == null || BubbleOverflow.KEY.equals(bubble.getKey());
float expandedViewHeight = getExpandedViewHeight(bubble);
- float topAlignment = getExpandedViewYTopAligned();
+ int topAlignment = getExpandedViewYTopAligned();
int manageButtonHeight =
isOverflow ? mExpandedViewPadding : mManageButtonHeightIncludingMargins;
- // On largescreen portrait bubbles are bottom aligned.
+ // On large screen portrait bubbles are bottom aligned.
if (areBubblesBottomAligned() && expandedViewHeight == MAX_HEIGHT) {
return mPositionRect.bottom - manageButtonHeight
- getExpandedViewHeightForLargeScreen() - mPointerWidth;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
index 02af2d06a1dd..7798aa753aa2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
@@ -420,7 +420,7 @@ public class ExpandedAnimationController
bubbleView.setTranslationY(y);
}
- final float expandedY = mPositioner.getExpandedViewYTopAligned();
+ final int expandedY = mPositioner.getExpandedViewYTopAligned();
final boolean draggedOutEnough =
y > expandedY + mBubbleSizePx || y < expandedY - mBubbleSizePx;
if (draggedOutEnough != mBubbleDraggedOutEnough) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
index 473deba3b7d2..af31f5f27fe9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.transition;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
import static com.android.wm.shell.transition.Transitions.TransitionObserver;
@@ -35,7 +36,8 @@ import com.android.wm.shell.util.TransitionUtil;
/**
* The {@link TransitionObserver} that observes for transitions involving the home
- * activity. It reports transitions to the caller via {@link IHomeTransitionListener}.
+ * activity on the {@link android.view.Display#DEFAULT_DISPLAY} only.
+ * It reports transitions to the caller via {@link IHomeTransitionListener}.
*/
public class HomeTransitionObserver implements TransitionObserver,
RemoteCallable<HomeTransitionObserver> {
@@ -58,6 +60,7 @@ public class HomeTransitionObserver implements TransitionObserver,
for (TransitionInfo.Change change : info.getChanges()) {
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
if (taskInfo == null
+ || taskInfo.displayId != DEFAULT_DISPLAY
|| taskInfo.taskId == -1
|| !taskInfo.isRunning) {
continue;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
index 18716c68da27..72fba3bb7de4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
@@ -20,12 +20,13 @@ import android.window.RemoteTransition;
import android.window.TransitionFilter;
/**
- * Listener interface that Launcher attaches to SystemUI to get home activity transition callbacks.
+ * Listener interface that Launcher attaches to SystemUI to get home activity transition callbacks
+ * on the default display.
*/
-interface IHomeTransitionListener {
+oneway interface IHomeTransitionListener {
/**
- * Called when a transition changes the visibility of the home activity.
+ * Called when a transition changes the visibility of the home activity on the default display.
*/
void onHomeVisibilityChanged(in boolean isVisible);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index c12ac8b3772e..6e7d11d9082b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -22,6 +22,7 @@ import android.app.WindowConfiguration.WindowingMode;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
+import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.VectorDrawable;
import android.os.Handler;
@@ -34,6 +35,7 @@ import android.window.WindowContainerTransaction;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
/**
@@ -84,6 +86,69 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
mDragPositioningCallback = dragPositioningCallback;
}
+ @Override
+ Rect calculateValidDragArea() {
+ final int leftButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
+ R.dimen.caption_left_buttons_width);
+
+ // On a smaller screen, don't require as much empty space on screen, as offscreen
+ // drags will be restricted too much.
+ final int requiredEmptySpaceId = mDisplayController.getDisplayContext(mTaskInfo.taskId)
+ .getResources().getConfiguration().smallestScreenWidthDp >= 600
+ ? R.dimen.freeform_required_visible_empty_space_in_header :
+ R.dimen.small_screen_required_visible_empty_space_in_header;
+ final int requiredEmptySpace = loadDimensionPixelSize(mContext.getResources(),
+ requiredEmptySpaceId);
+
+ final int rightButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
+ R.dimen.caption_right_buttons_width);
+ final int taskWidth = mTaskInfo.configuration.windowConfiguration.getBounds().width();
+ final DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
+ final int displayWidth = layout.width();
+ final Rect stableBounds = new Rect();
+ layout.getStableBounds(stableBounds);
+ return new Rect(
+ determineMinX(leftButtonsWidth, rightButtonsWidth, requiredEmptySpace,
+ taskWidth),
+ stableBounds.top,
+ determineMaxX(leftButtonsWidth, rightButtonsWidth, requiredEmptySpace, taskWidth,
+ displayWidth),
+ determineMaxY(requiredEmptySpace, stableBounds));
+ }
+
+
+ /**
+ * Determine the lowest x coordinate of a freeform task. Used for restricting drag inputs.
+ */
+ private int determineMinX(int leftButtonsWidth, int rightButtonsWidth, int requiredEmptySpace,
+ int taskWidth) {
+ // Do not let apps with < 48dp empty header space go off the left edge at all.
+ if (leftButtonsWidth + rightButtonsWidth + requiredEmptySpace > taskWidth) {
+ return 0;
+ }
+ return -taskWidth + requiredEmptySpace + rightButtonsWidth;
+ }
+
+ /**
+ * Determine the highest x coordinate of a freeform task. Used for restricting drag inputs.
+ */
+ private int determineMaxX(int leftButtonsWidth, int rightButtonsWidth, int requiredEmptySpace,
+ int taskWidth, int displayWidth) {
+ // Do not let apps with < 48dp empty header space go off the right edge at all.
+ if (leftButtonsWidth + rightButtonsWidth + requiredEmptySpace > taskWidth) {
+ return displayWidth - taskWidth;
+ }
+ return displayWidth - requiredEmptySpace - leftButtonsWidth;
+ }
+
+ /**
+ * Determine the highest y coordinate of a freeform task. Used for restricting drag inputs.
+ */
+ private int determineMaxY(int requiredEmptySpace, Rect stableBounds) {
+ return stableBounds.bottom - requiredEmptySpace;
+ }
+
+
void setDragDetector(DragDetector dragDetector) {
mDragDetector = dragDetector;
mDragDetector.setTouchSlop(ViewConfiguration.get(mContext).getScaledTouchSlop());
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 6ec91e0e28dd..3b6be8afb9e7 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
@@ -443,6 +443,66 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
/**
+ * Determine valid drag area for this task based on elements in the app chip.
+ */
+ @Override
+ Rect calculateValidDragArea() {
+ final int appTextWidth = ((DesktopModeAppControlsWindowDecorationViewHolder)
+ mWindowDecorViewHolder).getAppNameTextWidth();
+ final int leftButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
+ R.dimen.desktop_mode_app_details_width_minus_text) + appTextWidth;
+ final int requiredEmptySpace = loadDimensionPixelSize(mContext.getResources(),
+ R.dimen.freeform_required_visible_empty_space_in_header);
+ final int rightButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
+ R.dimen.desktop_mode_right_edge_buttons_width);
+ final int taskWidth = mTaskInfo.configuration.windowConfiguration.getBounds().width();
+ final DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
+ final int displayWidth = layout.width();
+ final Rect stableBounds = new Rect();
+ layout.getStableBounds(stableBounds);
+ return new Rect(
+ determineMinX(leftButtonsWidth, rightButtonsWidth, requiredEmptySpace,
+ taskWidth),
+ stableBounds.top,
+ determineMaxX(leftButtonsWidth, rightButtonsWidth, requiredEmptySpace,
+ taskWidth, displayWidth),
+ determineMaxY(requiredEmptySpace, stableBounds));
+ }
+
+
+ /**
+ * Determine the lowest x coordinate of a freeform task. Used for restricting drag inputs.
+ */
+ private int determineMinX(int leftButtonsWidth, int rightButtonsWidth, int requiredEmptySpace,
+ int taskWidth) {
+ // Do not let apps with < 48dp empty header space go off the left edge at all.
+ if (leftButtonsWidth + rightButtonsWidth + requiredEmptySpace > taskWidth) {
+ return 0;
+ }
+ return -taskWidth + requiredEmptySpace + rightButtonsWidth;
+ }
+
+ /**
+ * Determine the highest x coordinate of a freeform task. Used for restricting drag inputs.
+ */
+ private int determineMaxX(int leftButtonsWidth, int rightButtonsWidth, int requiredEmptySpace,
+ int taskWidth, int displayWidth) {
+ // Do not let apps with < 48dp empty header space go off the right edge at all.
+ if (leftButtonsWidth + rightButtonsWidth + requiredEmptySpace > taskWidth) {
+ return displayWidth - taskWidth;
+ }
+ return displayWidth - requiredEmptySpace - leftButtonsWidth;
+ }
+
+ /**
+ * Determine the highest y coordinate of a freeform task. Used for restricting drag inputs.
+ */
+ private int determineMaxY(int requiredEmptySpace, Rect stableBounds) {
+ return stableBounds.bottom - requiredEmptySpace;
+ }
+
+
+ /**
* Create and display maximize menu window
*/
void createMaximizeMenu() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
index cb0a6c733fe3..677c7f1fb5a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
@@ -162,18 +162,29 @@ public class DragPositioningCallbackUtility {
/**
* Updates repositionTaskBounds to the final bounds of the task after the drag is finished. If
- * the bounds are outside of the stable bounds, they are shifted to place task at the top of the
- * stable bounds.
+ * the bounds are outside of the valid drag area, the task is shifted back onto the edge of the
+ * valid drag area.
*/
- static void onDragEnd(Rect repositionTaskBounds, Rect taskBoundsAtDragStart, Rect stableBounds,
- PointF repositionStartPoint, float x, float y) {
+ static void onDragEnd(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
+ PointF repositionStartPoint, float x, float y, Rect validDragArea) {
updateTaskBounds(repositionTaskBounds, taskBoundsAtDragStart, repositionStartPoint,
x, y);
+ snapTaskBoundsIfNecessary(repositionTaskBounds, validDragArea);
+ }
- // If task is outside of stable bounds (in the status bar area), shift the task down.
- if (stableBounds.top > repositionTaskBounds.top) {
- final int yShift = stableBounds.top - repositionTaskBounds.top;
- repositionTaskBounds.offset(0, yShift);
+ private static void snapTaskBoundsIfNecessary(Rect repositionTaskBounds, Rect validDragArea) {
+ // If we were never supplied a valid drag area, do not restrict movement.
+ // Otherwise, we restrict deltas to keep task position inside the Rect.
+ if (validDragArea.width() == 0) return;
+ if (repositionTaskBounds.left < validDragArea.left) {
+ repositionTaskBounds.offset(validDragArea.left - repositionTaskBounds.left, 0);
+ } else if (repositionTaskBounds.left > validDragArea.right) {
+ repositionTaskBounds.offset(validDragArea.right - repositionTaskBounds.left, 0);
+ }
+ if (repositionTaskBounds.top < validDragArea.top) {
+ repositionTaskBounds.offset(0, validDragArea.top - repositionTaskBounds.top);
+ } else if (repositionTaskBounds.top > validDragArea.bottom) {
+ repositionTaskBounds.offset(0, validDragArea.bottom - repositionTaskBounds.top);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
index 3a1ea0e201b2..5d006fb4d9e2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
@@ -136,7 +136,8 @@ class FluidResizeTaskPositioner implements DragPositioningCallback {
y)) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
- mTaskBoundsAtDragStart, mStableBounds, mRepositionStartPoint, x, y);
+ mTaskBoundsAtDragStart, mRepositionStartPoint, x, y,
+ mWindowDecoration.calculateValidDragArea());
wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
mTaskOrganizer.applyTransaction(wct);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index 4b55a0caaca5..4363558ca00b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -152,7 +152,8 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
mDisallowedAreaForEndBoundsHeight, mTaskBoundsAtDragStart, mRepositionStartPoint,
y)) {
DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
- mTaskBoundsAtDragStart, mStableBounds, mRepositionStartPoint, x, y);
+ mTaskBoundsAtDragStart, mRepositionStartPoint, x, y,
+ mDesktopWindowDecoration.calculateValidDragArea());
DragPositioningCallbackUtility.applyTaskBoundsChange(new WindowContainerTransaction(),
mDesktopWindowDecoration, mRepositionTaskBounds, mTaskOrganizer);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 634b7558c7d8..ee0e31ec3aef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -21,6 +21,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowInsets.Type.statusBars;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Context;
@@ -178,6 +179,13 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
*/
abstract void relayout(RunningTaskInfo taskInfo);
+ /**
+ * Used by the {@link DragPositioningCallback} associated with the implementing class to
+ * enforce drags ending in a valid position. A null result means no restriction.
+ */
+ @Nullable
+ abstract Rect calculateValidDragArea();
+
void relayout(RelayoutParams params, SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT, WindowContainerTransaction wct, T rootView,
RelayoutResult<T> outResult) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
index 589a8134c2d3..144373f3550e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
@@ -42,6 +42,8 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder(
private val maximizeWindowButton: ImageButton = rootView.requireViewById(R.id.maximize_window)
private val appNameTextView: TextView = rootView.requireViewById(R.id.application_name)
private val appIconImageView: ImageView = rootView.requireViewById(R.id.application_icon)
+ val appNameTextWidth: Int
+ get() = appNameTextView.width
init {
captionView.setOnTouchListener(onCaptionTouchListener)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
index 5c0e04aecf6c..e60be7186b1e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
@@ -181,6 +181,26 @@ class DragPositioningCallbackUtilityTest {
}
@Test
+ fun testDragEndSnapsTaskBoundsWhenOutsideValidDragArea() {
+ val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat())
+ val repositionTaskBounds = Rect(STARTING_BOUNDS)
+ val validDragArea = Rect(DISPLAY_BOUNDS.left - 100,
+ STABLE_BOUNDS.top,
+ DISPLAY_BOUNDS.right - 100,
+ DISPLAY_BOUNDS.bottom - 100)
+
+ DragPositioningCallbackUtility.onDragEnd(repositionTaskBounds, STARTING_BOUNDS,
+ startingPoint, startingPoint.x - 1000, (DISPLAY_BOUNDS.bottom + 1000).toFloat(),
+ validDragArea)
+ assertThat(repositionTaskBounds.left).isEqualTo(validDragArea.left)
+ assertThat(repositionTaskBounds.top).isEqualTo(validDragArea.bottom)
+ assertThat(repositionTaskBounds.right)
+ .isEqualTo(validDragArea.left + STARTING_BOUNDS.width())
+ assertThat(repositionTaskBounds.bottom)
+ .isEqualTo(validDragArea.bottom + STARTING_BOUNDS.height())
+ }
+
+ @Test
fun testChangeBounds_toDisallowedBounds_freezesAtLimit() {
var hasMoved = false
val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
index add78b2ee8b3..2ce49cf62614 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
@@ -103,6 +103,7 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
configuration.windowConfiguration.displayRotation = ROTATION_90
}
+ `when`(mockWindowDecoration.calculateValidDragArea()).thenReturn(VALID_DRAG_AREA)
mockWindowDecoration.mDisplay = mockDisplay
whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
@@ -660,6 +661,38 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
}
@Test
+ fun testDragResize_drag_taskPositionedInValidDragArea() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_UNDEFINED, // drag
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ val newX = VALID_DRAG_AREA.left - 500f
+ val newY = VALID_DRAG_AREA.bottom + 500f
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+ verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
+
+ taskPositioner.onDragPositioningEnd(
+ newX,
+ newY
+ )
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds.top ==
+ VALID_DRAG_AREA.bottom &&
+ change.configuration.windowConfiguration.bounds.left ==
+ VALID_DRAG_AREA.left
+ }
+ })
+ }
+
+ @Test
fun testDragResize_drag_updatesStableBoundsOnRotate() {
// Test landscape stable bounds
performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
@@ -761,5 +794,11 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
DISPLAY_BOUNDS.bottom,
DISPLAY_BOUNDS.right - NAVBAR_HEIGHT
)
+ private val VALID_DRAG_AREA = Rect(
+ DISPLAY_BOUNDS.left - 100,
+ STABLE_BOUNDS_LANDSCAPE.top,
+ DISPLAY_BOUNDS.right - 100,
+ DISPLAY_BOUNDS.bottom - 100
+ )
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index a70ebf14324a..a759b53f4238 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -118,6 +118,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
configuration.windowConfiguration.displayRotation = ROTATION_90
}
+ `when`(mockDesktopWindowDecoration.calculateValidDragArea()).thenReturn(VALID_DRAG_AREA)
mockDesktopWindowDecoration.mDisplay = mockDisplay
whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
@@ -379,6 +380,38 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
}
@Test
+ fun testDragResize_drag_taskPositionedInValidDragArea() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_UNDEFINED, // drag
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ val newX = VALID_DRAG_AREA.left - 500f
+ val newY = VALID_DRAG_AREA.bottom + 500f
+ taskPositioner.onDragPositioningMove(
+ newX,
+ newY
+ )
+ verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
+
+ taskPositioner.onDragPositioningEnd(
+ newX,
+ newY
+ )
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds.top ==
+ VALID_DRAG_AREA.bottom &&
+ change.configuration.windowConfiguration.bounds.left ==
+ VALID_DRAG_AREA.left
+ }
+ })
+ }
+
+ @Test
fun testDragResize_drag_updatesStableBoundsOnRotate() {
// Test landscape stable bounds
performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
@@ -470,5 +503,11 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
DISPLAY_BOUNDS.bottom,
DISPLAY_BOUNDS.right - NAVBAR_HEIGHT
)
+ private val VALID_DRAG_AREA = Rect(
+ DISPLAY_BOUNDS.left - 100,
+ STABLE_BOUNDS_LANDSCAPE.top,
+ DISPLAY_BOUNDS.right - 100,
+ DISPLAY_BOUNDS.bottom - 100
+ )
}
}
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 8e42f74b8d17..fe508e23af33 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
@@ -702,6 +702,11 @@ public class WindowDecorationTests extends ShellTestCase {
relayout(taskInfo, false /* applyStartTransactionOnDraw */);
}
+ @Override
+ Rect calculateValidDragArea() {
+ return null;
+ }
+
void relayout(ActivityManager.RunningTaskInfo taskInfo,
boolean applyStartTransactionOnDraw) {
mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
diff --git a/location/java/android/location/LocationResult.java b/location/java/android/location/LocationResult.java
index 8423000b6276..67f47756984f 100644
--- a/location/java/android/location/LocationResult.java
+++ b/location/java/android/location/LocationResult.java
@@ -19,8 +19,11 @@ package android.location;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.location.flags.Flags;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.Log;
import com.android.internal.util.Preconditions;
@@ -37,6 +40,23 @@ import java.util.function.Predicate;
* @hide
*/
public final class LocationResult implements Parcelable {
+ private static final String TAG = "LocationResult";
+
+ // maximum reasonable accuracy, somewhat arbitrarily chosen. this is a very high upper limit, it
+ // could likely be lower, but we only want to throw out really absurd values.
+ private static final float MAX_ACCURACY_M = 1000000;
+
+ // maximum reasonable speed we expect a device to travel at is currently mach 1 (top speed of
+ // current fastest private jet). Higher speed than the value is considered as a malfunction
+ // than a correct reading.
+ private static final float MAX_SPEED_MPS = 343;
+
+ /** Exception representing an invalid location within a {@link LocationResult}. */
+ public static class BadLocationException extends Exception {
+ public BadLocationException(String message) {
+ super(message);
+ }
+ }
/**
* Creates a new LocationResult from the given locations, making a copy of each location.
@@ -101,18 +121,60 @@ public final class LocationResult implements Parcelable {
*
* @hide
*/
- public @NonNull LocationResult validate() {
+ public @NonNull LocationResult validate() throws BadLocationException {
long prevElapsedRealtimeNs = 0;
final int size = mLocations.size();
for (int i = 0; i < size; ++i) {
Location location = mLocations.get(i);
- if (!location.isComplete()) {
- throw new IllegalArgumentException(
- "incomplete location at index " + i + ": " + mLocations);
- }
- if (location.getElapsedRealtimeNanos() < prevElapsedRealtimeNs) {
- throw new IllegalArgumentException(
- "incorrectly ordered location at index " + i + ": " + mLocations);
+ if (Flags.locationValidation()) {
+ if (location.getLatitude() < -90.0
+ || location.getLatitude() > 90.0
+ || location.getLongitude() < -180.0
+ || location.getLongitude() > 180.0
+ || Double.isNaN(location.getLatitude())
+ || Double.isNaN(location.getLongitude())) {
+ throw new BadLocationException("location must have valid lat/lng");
+ }
+ if (!location.hasAccuracy()) {
+ throw new BadLocationException("location must have accuracy");
+ }
+ if (location.getAccuracy() < 0 || location.getAccuracy() > MAX_ACCURACY_M) {
+ throw new BadLocationException("location must have reasonable accuracy");
+ }
+ if (location.getTime() < 0) {
+ throw new BadLocationException("location must have valid time");
+ }
+ if (prevElapsedRealtimeNs > location.getElapsedRealtimeNanos()) {
+ throw new BadLocationException(
+ "location must have valid monotonically increasing realtime");
+ }
+ if (location.getElapsedRealtimeNanos()
+ > SystemClock.elapsedRealtimeNanos()) {
+ throw new BadLocationException("location must not have realtime in the future");
+ }
+ if (!location.isMock()) {
+ if (location.getProvider() == null) {
+ throw new BadLocationException("location must have valid provider");
+ }
+ if (location.getLatitude() == 0 && location.getLongitude() == 0) {
+ throw new BadLocationException("location must not be at 0,0");
+ }
+ }
+
+ if (location.hasSpeed() && (location.getSpeed() < 0
+ || location.getSpeed() > MAX_SPEED_MPS)) {
+ Log.w(TAG, "removed bad location speed: " + location.getSpeed());
+ location.removeSpeed();
+ }
+ } else {
+ if (!location.isComplete()) {
+ throw new IllegalArgumentException(
+ "incomplete location at index " + i + ": " + mLocations);
+ }
+ if (location.getElapsedRealtimeNanos() < prevElapsedRealtimeNs) {
+ throw new IllegalArgumentException(
+ "incorrectly ordered location at index " + i + ": " + mLocations);
+ }
}
prevElapsedRealtimeNs = location.getElapsedRealtimeNanos();
}
diff --git a/location/java/android/location/flags/gnss.aconfig b/location/java/android/location/flags/gnss.aconfig
index f4b1056019cb..a8464d3f86ec 100644
--- a/location/java/android/location/flags/gnss.aconfig
+++ b/location/java/android/location/flags/gnss.aconfig
@@ -27,3 +27,10 @@ flag {
description: "Flag for releasing SUPL connection on timeout"
bug: "315024652"
}
+
+flag {
+ name: "location_validation"
+ namespace: "location"
+ description: "Flag for location validation"
+ bug: "314328533"
+}
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 3da52ccbafed..7f9588644052 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -77,3 +77,9 @@ flag {
bug: "279555229"
}
+flag {
+ name: "enable_notifying_activity_manager_with_media_session_status_change"
+ namespace: "media_solutions"
+ description: "Notify ActivityManager with the changes in playback state of the media session."
+ bug: "295518668"
+}
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index 7891ee6d5861..60497fe22dcf 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -524,6 +524,28 @@ public final class PlaybackState implements Parcelable {
return false;
}
+ /**
+ * Returns whether the service holding the media session should run in the foreground when the
+ * media session has this playback state or not.
+ *
+ * @hide
+ */
+ public boolean shouldAllowServiceToRunInForeground() {
+ switch (mState) {
+ case PlaybackState.STATE_PLAYING:
+ case PlaybackState.STATE_FAST_FORWARDING:
+ case PlaybackState.STATE_REWINDING:
+ case PlaybackState.STATE_BUFFERING:
+ case PlaybackState.STATE_CONNECTING:
+ case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
+ case PlaybackState.STATE_SKIPPING_TO_NEXT:
+ case PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM:
+ return true;
+ default:
+ return false;
+ }
+ }
+
public static final @android.annotation.NonNull Parcelable.Creator<PlaybackState> CREATOR =
new Parcelable.Creator<PlaybackState>() {
@Override
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 757e9f8e41b1..3fcb8713672f 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -693,6 +693,8 @@ void FilterClientCallbackImpl::getMediaEvent(const jobjectArray& arr, const int
mpuSequenceNumber, isPesPrivateData, sc,
audioDescriptor.get(), presentationsJObj.get()));
+ // Protect mFilterClient from being set to null.
+ android::Mutex::Autolock autoLock(mLock);
uint64_t avSharedMemSize = mFilterClient->getAvSharedHandleInfo().size;
if (mediaEvent.avMemory.fds.size() > 0 || mediaEvent.avDataId != 0 ||
(dataLength > 0 && (dataLength + offset) < avSharedMemSize)) {
@@ -939,38 +941,52 @@ void FilterClientCallbackImpl::onFilterEvent(const vector<DemuxFilterEvent> &eve
}
}
}
- ScopedLocalRef filter(env, env->NewLocalRef(mFilterObj));
- if (!env->IsSameObject(filter.get(), nullptr)) {
- jmethodID methodID = gFields.onFilterEventID;
- if (mSharedFilter) {
- methodID = gFields.onSharedFilterEventID;
+
+ ScopedLocalRef<jobject> filter(env);
+ {
+ android::Mutex::Autolock autoLock(mLock);
+ if (env->IsSameObject(mFilterObj, nullptr)) {
+ ALOGE("FilterClientCallbackImpl::onFilterEvent:"
+ "Filter object has been freed. Ignoring callback.");
+ return;
+ } else {
+ filter.reset(env->NewLocalRef(mFilterObj));
}
- env->CallVoidMethod(filter.get(), methodID, array.get());
- } else {
- ALOGE("FilterClientCallbackImpl::onFilterEvent:"
- "Filter object has been freed. Ignoring callback.");
}
+
+ jmethodID methodID = gFields.onFilterEventID;
+ if (mSharedFilter) {
+ methodID = gFields.onSharedFilterEventID;
+ }
+ env->CallVoidMethod(filter.get(), methodID, array.get());
}
void FilterClientCallbackImpl::onFilterStatus(const DemuxFilterStatus status) {
ALOGV("FilterClientCallbackImpl::onFilterStatus");
JNIEnv *env = AndroidRuntime::getJNIEnv();
- ScopedLocalRef filter(env, env->NewLocalRef(mFilterObj));
- if (!env->IsSameObject(filter.get(), nullptr)) {
- jmethodID methodID = gFields.onFilterStatusID;
- if (mSharedFilter) {
- methodID = gFields.onSharedFilterStatusID;
+ ScopedLocalRef<jobject> filter(env);
+ {
+ android::Mutex::Autolock autoLock(mLock);
+ if (env->IsSameObject(filter.get(), nullptr)) {
+ ALOGE("FilterClientCallbackImpl::onFilterStatus:"
+ "Filter object has been freed. Ignoring callback.");
+ return;
+ } else {
+ filter.reset(env->NewLocalRef(mFilterObj));
}
- env->CallVoidMethod(filter.get(), methodID, (jint)static_cast<uint8_t>(status));
- } else {
- ALOGE("FilterClientCallbackImpl::onFilterStatus:"
- "Filter object has been freed. Ignoring callback.");
}
+
+ jmethodID methodID = gFields.onFilterStatusID;
+ if (mSharedFilter) {
+ methodID = gFields.onSharedFilterStatusID;
+ }
+ env->CallVoidMethod(filter.get(), methodID, (jint)static_cast<uint8_t>(status));
}
void FilterClientCallbackImpl::setFilter(jweak filterObj, sp<FilterClient> filterClient) {
ALOGV("FilterClientCallbackImpl::setFilter");
// Java Object
+ android::Mutex::Autolock autoLock(mLock);
mFilterObj = filterObj;
mFilterClient = filterClient;
mSharedFilter = false;
@@ -979,6 +995,7 @@ void FilterClientCallbackImpl::setFilter(jweak filterObj, sp<FilterClient> filte
void FilterClientCallbackImpl::setSharedFilter(jweak filterObj, sp<FilterClient> filterClient) {
ALOGV("FilterClientCallbackImpl::setFilter");
// Java Object
+ android::Mutex::Autolock autoLock(mLock);
mFilterObj = filterObj;
mFilterClient = filterClient;
mSharedFilter = true;
@@ -1047,11 +1064,14 @@ FilterClientCallbackImpl::FilterClientCallbackImpl() {
FilterClientCallbackImpl::~FilterClientCallbackImpl() {
JNIEnv *env = AndroidRuntime::getJNIEnv();
- if (mFilterObj != nullptr) {
- env->DeleteWeakGlobalRef(mFilterObj);
- mFilterObj = nullptr;
+ {
+ android::Mutex::Autolock autoLock(mLock);
+ if (mFilterObj != nullptr) {
+ env->DeleteWeakGlobalRef(mFilterObj);
+ mFilterObj = nullptr;
+ }
+ mFilterClient = nullptr;
}
- mFilterClient = nullptr;
env->DeleteGlobalRef(mEventClass);
env->DeleteGlobalRef(mSectionEventClass);
env->DeleteGlobalRef(mMediaEventClass);
@@ -3696,7 +3716,7 @@ static void android_media_tv_Tuner_native_init(JNIEnv *env) {
"([Landroid/media/tv/tuner/filter/FilterEvent;)V");
jclass sharedFilterClazz = env->FindClass("android/media/tv/tuner/filter/SharedFilter");
- gFields.sharedFilterContext = env->GetFieldID(filterClazz, "mNativeContext", "J");
+ gFields.sharedFilterContext = env->GetFieldID(sharedFilterClazz, "mNativeContext", "J");
gFields.sharedFilterInitID = env->GetMethodID(sharedFilterClazz, "<init>", "()V");
gFields.onSharedFilterStatusID = env->GetMethodID(sharedFilterClazz, "onFilterStatus", "(I)V");
gFields.onSharedFilterEventID =
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index 01c998dab8d6..3de3ab91326c 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -136,6 +136,7 @@ struct FilterClientCallbackImpl : public FilterClientCallback {
private:
jweak mFilterObj;
sp<FilterClient> mFilterClient;
+ android::Mutex mLock;
jclass mEventClass;
jclass mSectionEventClass;
jclass mMediaEventClass;
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index c4c81284780e..abe4a3d18b38 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -18,10 +18,12 @@
#include <aidl/android/hardware/power/SessionHint.h>
#include <aidl/android/hardware/power/SessionMode.h>
+#include <android-base/stringprintf.h>
#include <android/WorkDuration.h>
#include <android/os/IHintManager.h>
#include <android/os/IHintSession.h>
#include <android/performance_hint.h>
+#include <android/trace.h>
#include <binder/Binder.h>
#include <binder/IBinder.h>
#include <binder/IServiceManager.h>
@@ -30,6 +32,7 @@
#include <utils/SystemClock.h>
#include <chrono>
+#include <set>
#include <utility>
#include <vector>
@@ -40,6 +43,7 @@ using namespace std::chrono_literals;
using AidlSessionHint = aidl::android::hardware::power::SessionHint;
using AidlSessionMode = aidl::android::hardware::power::SessionMode;
+using android::base::StringPrintf;
struct APerformanceHintSession;
@@ -98,10 +102,21 @@ private:
std::vector<int64_t> mLastHintSentTimestamp;
// Cached samples
std::vector<WorkDuration> mActualWorkDurations;
+ std::string mSessionName;
+ static int32_t sIDCounter;
+ // The most recent set of thread IDs
+ std::vector<int32_t> mLastThreadIDs;
+ // Tracing helpers
+ void traceThreads(std::vector<int32_t>& tids);
+ void tracePowerEfficient(bool powerEfficient);
+ void traceActualDuration(int64_t actualDuration);
+ void traceBatchSize(size_t batchSize);
+ void traceTargetDuration(int64_t targetDuration);
};
static IHintManager* gIHintManagerForTesting = nullptr;
static APerformanceHintManager* gHintManagerForTesting = nullptr;
+int32_t APerformanceHintSession::sIDCounter = 0;
// ===================================== APerformanceHintManager implementation
APerformanceHintManager::APerformanceHintManager(sp<IHintManager> manager,
@@ -150,8 +165,12 @@ APerformanceHintSession* APerformanceHintManager::createSession(
if (!ret.isOk() || !session) {
return nullptr;
}
- return new APerformanceHintSession(mHintManager, std::move(session), mPreferredRateNanos,
- initialTargetWorkDurationNanos);
+ auto out = new APerformanceHintSession(mHintManager, std::move(session), mPreferredRateNanos,
+ initialTargetWorkDurationNanos);
+ out->traceThreads(tids);
+ out->traceTargetDuration(initialTargetWorkDurationNanos);
+ out->tracePowerEfficient(false);
+ return out;
}
int64_t APerformanceHintManager::getPreferredRateNanos() const {
@@ -174,6 +193,7 @@ APerformanceHintSession::APerformanceHintSession(sp<IHintManager> hintManager,
ndk::enum_range<AidlSessionHint>().end()};
mLastHintSentTimestamp = std::vector<int64_t>(sessionHintRange.size(), 0);
+ mSessionName = android::base::StringPrintf("ADPF Session %" PRId32, ++sIDCounter);
}
APerformanceHintSession::~APerformanceHintSession() {
@@ -200,6 +220,8 @@ int APerformanceHintSession::updateTargetWorkDuration(int64_t targetDurationNano
* as they are most likely obsolete.
*/
mActualWorkDurations.clear();
+ traceBatchSize(0);
+ traceTargetDuration(targetDurationNanos);
mFirstTargetMetTimestamp = 0;
mLastTargetMetTimestamp = 0;
return 0;
@@ -254,6 +276,9 @@ int APerformanceHintSession::setThreads(const int32_t* threadIds, size_t size) {
}
return EPIPE;
}
+
+ traceThreads(tids);
+
return 0;
}
@@ -289,6 +314,7 @@ int APerformanceHintSession::setPreferPowerEfficiency(bool enabled) {
ret.exceptionMessage().c_str());
return EPIPE;
}
+ tracePowerEfficient(enabled);
return OK;
}
@@ -318,6 +344,7 @@ int APerformanceHintSession::reportActualWorkDurationInternal(WorkDuration* work
int64_t actualTotalDurationNanos = workDuration->actualTotalDurationNanos;
int64_t now = uptimeNanos();
workDuration->timestampNanos = now;
+ traceActualDuration(workDuration->actualTotalDurationNanos);
mActualWorkDurations.push_back(std::move(*workDuration));
if (actualTotalDurationNanos >= mTargetDurationNanos) {
@@ -335,6 +362,7 @@ int APerformanceHintSession::reportActualWorkDurationInternal(WorkDuration* work
*/
if (now - mFirstTargetMetTimestamp > mPreferredRateNanos &&
now - mLastTargetMetTimestamp <= mPreferredRateNanos) {
+ traceBatchSize(mActualWorkDurations.size());
return 0;
}
mLastTargetMetTimestamp = now;
@@ -346,12 +374,54 @@ int APerformanceHintSession::reportActualWorkDurationInternal(WorkDuration* work
ret.exceptionMessage().c_str());
mFirstTargetMetTimestamp = 0;
mLastTargetMetTimestamp = 0;
+ traceBatchSize(mActualWorkDurations.size());
return ret.exceptionCode() == binder::Status::EX_ILLEGAL_ARGUMENT ? EINVAL : EPIPE;
}
mActualWorkDurations.clear();
+ traceBatchSize(0);
return 0;
}
+// ===================================== Tracing helpers
+
+void APerformanceHintSession::traceThreads(std::vector<int32_t>& tids) {
+ std::set<int32_t> tidSet{tids.begin(), tids.end()};
+
+ // Disable old TID tracing
+ for (int32_t tid : mLastThreadIDs) {
+ if (!tidSet.count(tid)) {
+ std::string traceName =
+ android::base::StringPrintf("%s TID: %" PRId32, mSessionName.c_str(), tid);
+ ATrace_setCounter(traceName.c_str(), 0);
+ }
+ }
+
+ // Add new TID tracing
+ for (int32_t tid : tids) {
+ std::string traceName =
+ android::base::StringPrintf("%s TID: %" PRId32, mSessionName.c_str(), tid);
+ ATrace_setCounter(traceName.c_str(), 1);
+ }
+
+ mLastThreadIDs = std::move(tids);
+}
+
+void APerformanceHintSession::tracePowerEfficient(bool powerEfficient) {
+ ATrace_setCounter((mSessionName + " power efficiency mode").c_str(), powerEfficient);
+}
+
+void APerformanceHintSession::traceActualDuration(int64_t actualDuration) {
+ ATrace_setCounter((mSessionName + " actual duration").c_str(), actualDuration);
+}
+
+void APerformanceHintSession::traceBatchSize(size_t batchSize) {
+ std::string traceName = StringPrintf("%s batch size", mSessionName.c_str());
+ ATrace_setCounter((mSessionName + " batch size").c_str(), batchSize);
+}
+
+void APerformanceHintSession::traceTargetDuration(int64_t targetDuration) {
+ ATrace_setCounter((mSessionName + " target duration").c_str(), targetDuration);
+}
// ===================================== C API
APerformanceHintManager* APerformanceHint_getManager() {
diff --git a/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml b/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml
index db8ebb43a11d..1ac5db673838 100644
--- a/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml
+++ b/packages/CompanionDeviceManager/res/layout/data_transfer_confirmation.xml
@@ -18,54 +18,63 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- style="@style/ScrollViewStyle">
+ style="@style/ScrollViewStyle"
+ android:importantForAccessibility="no">
<LinearLayout
- android:id="@+id/data_transfer_confirmation"
- style="@style/ContainerLayout">
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:baselineAligned="false"
+ android:importantForAccessibility="no">
- <!-- Do NOT change the ID of the root LinearLayout above: it's referenced in CTS tests. -->
+ <LinearLayout
+ android:id="@+id/data_transfer_confirmation"
+ style="@style/ContainerLayout">
- <ImageView
- android:id="@+id/header_icon"
- android:layout_width="match_parent"
- android:layout_height="32dp"
- android:gravity="center"
- android:layout_marginTop="18dp"
- android:src="@drawable/ic_warning"
- android:contentDescription="@null" />
+ <!-- Do NOT change the ID of the root LinearLayout above: it's referenced in CTS tests. -->
- <LinearLayout style="@style/Description">
+ <ImageView
+ android:id="@+id/header_icon"
+ android:layout_width="match_parent"
+ android:layout_height="32dp"
+ android:gravity="center"
+ android:layout_marginTop="18dp"
+ android:src="@drawable/ic_warning"
+ android:contentDescription="@null" />
- <TextView
- android:id="@+id/title"
- style="@style/DescriptionTitle" />
+ <LinearLayout style="@style/Description">
- <TextView
- android:id="@+id/summary"
- style="@style/DescriptionSummary" />
+ <TextView
+ android:id="@+id/title"
+ style="@style/DescriptionTitle" />
- </LinearLayout>
+ <TextView
+ android:id="@+id/summary"
+ style="@style/DescriptionSummary" />
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:orientation="vertical"
- android:layout_marginTop="12dp"
- android:layout_marginBottom="18dp">
-
- <!-- Do NOT change the IDs of the buttons: they are referenced in CTS tests. -->
-
- <Button
- android:id="@+id/btn_positive"
- style="@style/PositiveButton"
- android:text="@string/consent_yes" />
-
- <Button
- android:id="@+id/btn_negative"
- style="@style/NegativeButton"
- android:text="@string/consent_no" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:orientation="vertical"
+ android:layout_marginTop="12dp"
+ android:layout_marginBottom="18dp">
+
+ <!-- Do NOT change the IDs of the buttons: they are referenced in CTS tests. -->
+
+ <Button
+ android:id="@+id/btn_positive"
+ style="@style/PositiveButton"
+ android:text="@string/consent_yes" />
+
+ <Button
+ android:id="@+id/btn_negative"
+ style="@style/NegativeButton"
+ android:text="@string/consent_no" />
+
+ </LinearLayout>
</LinearLayout>
diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
index 933be11bfaf1..6a4bb216b495 100644
--- a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
+++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
@@ -134,8 +134,7 @@ public class LocalTransport extends BackupTransport {
@UsesReflection({
// As the runtime class name is used to generate the returned name, and the returned
// name may be used used with reflection, generate the necessary keep rules.
- @KeepTarget(classConstant = LocalTransport.class),
- @KeepTarget(extendsClassConstant = LocalTransport.class)
+ @KeepTarget(instanceOfClassConstant = LocalTransport.class)
})
@Override
public String name() {
diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp
index 25ad9b82189c..98a5a674fcdd 100644
--- a/packages/PackageInstaller/Android.bp
+++ b/packages/PackageInstaller/Android.bp
@@ -35,7 +35,10 @@ android_app {
name: "PackageInstaller",
defaults: ["platform_app_defaults"],
- srcs: ["src/**/*.java"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
certificate: "platform",
privileged: true,
@@ -62,7 +65,10 @@ android_app {
name: "PackageInstaller_tablet",
defaults: ["platform_app_defaults"],
- srcs: ["src/**/*.java"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
certificate: "platform",
privileged: true,
@@ -91,7 +97,10 @@ android_app {
name: "PackageInstaller_tv",
defaults: ["platform_app_defaults"],
- srcs: ["src/**/*.java"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
certificate: "platform",
privileged: true,
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.java
deleted file mode 100644
index c8175adc780e..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.java
+++ /dev/null
@@ -1,912 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.model;
-
-import static com.android.packageinstaller.v2.model.PackageUtil.canPackageQuery;
-import static com.android.packageinstaller.v2.model.PackageUtil.generateStubPackageInfo;
-import static com.android.packageinstaller.v2.model.PackageUtil.getAppSnippet;
-import static com.android.packageinstaller.v2.model.PackageUtil.getPackageInfo;
-import static com.android.packageinstaller.v2.model.PackageUtil.getPackageNameForUid;
-import static com.android.packageinstaller.v2.model.PackageUtil.isCallerSessionOwner;
-import static com.android.packageinstaller.v2.model.PackageUtil.isInstallPermissionGrantedOrRequested;
-import static com.android.packageinstaller.v2.model.PackageUtil.isPermissionGranted;
-import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.ABORT_REASON_DONE;
-import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.ABORT_REASON_INTERNAL_ERROR;
-import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.ABORT_REASON_POLICY;
-import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.DLG_PACKAGE_ERROR;
-import static com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired.USER_ACTION_REASON_ANONYMOUS_SOURCE;
-import static com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired.USER_ACTION_REASON_INSTALL_CONFIRMATION;
-import static com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired.USER_ACTION_REASON_UNKNOWN_SOURCE;
-
-import android.Manifest;
-import android.app.Activity;
-import android.app.AppOpsManager;
-import android.app.PendingIntent;
-import android.app.admin.DevicePolicyManager;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.InstallSourceInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageInstaller.SessionInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.ApplicationInfoFlags;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.AssetFileDescriptor;
-import android.net.Uri;
-import android.os.ParcelFileDescriptor;
-import android.os.Process;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.text.TextUtils;
-import android.util.EventLog;
-import android.util.Log;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.lifecycle.MutableLiveData;
-import com.android.packageinstaller.R;
-import com.android.packageinstaller.common.EventResultPersister;
-import com.android.packageinstaller.common.InstallEventReceiver;
-import com.android.packageinstaller.v2.model.PackageUtil.AppSnippet;
-import com.android.packageinstaller.v2.model.installstagedata.InstallAborted;
-import com.android.packageinstaller.v2.model.installstagedata.InstallFailed;
-import com.android.packageinstaller.v2.model.installstagedata.InstallInstalling;
-import com.android.packageinstaller.v2.model.installstagedata.InstallReady;
-import com.android.packageinstaller.v2.model.installstagedata.InstallStage;
-import com.android.packageinstaller.v2.model.installstagedata.InstallStaging;
-import com.android.packageinstaller.v2.model.installstagedata.InstallSuccess;
-import com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired;
-import java.io.File;
-import java.io.IOException;
-
-public class InstallRepository {
-
- public static final String EXTRA_STAGED_SESSION_ID =
- "com.android.packageinstaller.extra.STAGED_SESSION_ID";
- private static final String SCHEME_PACKAGE = "package";
- private static final String BROADCAST_ACTION =
- "com.android.packageinstaller.ACTION_INSTALL_COMMIT";
- private static final String TAG = InstallRepository.class.getSimpleName();
- private final Context mContext;
- private final PackageManager mPackageManager;
- private final PackageInstaller mPackageInstaller;
- private final UserManager mUserManager;
- private final DevicePolicyManager mDevicePolicyManager;
- private final AppOpsManager mAppOpsManager;
- private final MutableLiveData<InstallStage> mStagingResult = new MutableLiveData<>();
- private final MutableLiveData<InstallStage> mInstallResult = new MutableLiveData<>();
- private final boolean mLocalLOGV = false;
- private Intent mIntent;
- private boolean mIsSessionInstall;
- private boolean mIsTrustedSource;
- /**
- * Session ID for a session created when caller uses PackageInstaller APIs
- */
- private int mSessionId;
- /**
- * Session ID for a session created by this app
- */
- private int mStagedSessionId = SessionInfo.INVALID_ID;
- private int mCallingUid;
- private String mCallingPackage;
- private SessionStager mSessionStager;
- private AppOpRequestInfo mAppOpRequestInfo;
- private AppSnippet mAppSnippet;
- /**
- * PackageInfo of the app being installed on device.
- */
- private PackageInfo mNewPackageInfo;
-
- public InstallRepository(Context context) {
- mContext = context;
- mPackageManager = context.getPackageManager();
- mPackageInstaller = mPackageManager.getPackageInstaller();
- mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
- mUserManager = context.getSystemService(UserManager.class);
- mAppOpsManager = context.getSystemService(AppOpsManager.class);
- }
-
- /**
- * Extracts information from the incoming install intent, checks caller's permission to install
- * packages, verifies that the caller is the install session owner (in case of a session based
- * install) and checks if the current user has restrictions set that prevent app installation,
- *
- * @param intent the incoming {@link Intent} object for installing a package
- * @param callerInfo {@link CallerInfo} that holds the callingUid and callingPackageName
- * @return <p>{@link InstallAborted} if there are errors while performing the checks</p>
- * <p>{@link InstallStaging} after successfully performing the checks</p>
- */
- public InstallStage performPreInstallChecks(Intent intent, CallerInfo callerInfo) {
- mIntent = intent;
-
- String callingAttributionTag = null;
-
- mIsSessionInstall =
- PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL.equals(intent.getAction())
- || PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction());
-
- mSessionId = mIsSessionInstall
- ? intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, SessionInfo.INVALID_ID)
- : SessionInfo.INVALID_ID;
-
- mStagedSessionId = mIntent.getIntExtra(EXTRA_STAGED_SESSION_ID, SessionInfo.INVALID_ID);
-
- mCallingPackage = callerInfo.getPackageName();
-
- if (mCallingPackage == null && mSessionId != SessionInfo.INVALID_ID) {
- PackageInstaller.SessionInfo sessionInfo = mPackageInstaller.getSessionInfo(mSessionId);
- mCallingPackage = (sessionInfo != null) ? sessionInfo.getInstallerPackageName() : null;
- callingAttributionTag =
- (sessionInfo != null) ? sessionInfo.getInstallerAttributionTag() : null;
- }
-
- // Uid of the source package, coming from ActivityManager
- mCallingUid = callerInfo.getUid();
- if (mCallingUid == Process.INVALID_UID) {
- Log.e(TAG, "Could not determine the launching uid.");
- }
- final ApplicationInfo sourceInfo = getSourceInfo(mCallingPackage);
- // Uid of the source package, with a preference to uid from ApplicationInfo
- final int originatingUid = sourceInfo != null ? sourceInfo.uid : mCallingUid;
- mAppOpRequestInfo = new AppOpRequestInfo(
- getPackageNameForUid(mContext, originatingUid, mCallingPackage),
- originatingUid, callingAttributionTag);
-
- if (mCallingUid == Process.INVALID_UID && sourceInfo == null) {
- // Caller's identity could not be determined. Abort the install
- return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
- }
-
- if ((mSessionId != SessionInfo.INVALID_ID
- && !isCallerSessionOwner(mPackageInstaller, originatingUid, mSessionId))
- || (mStagedSessionId != SessionInfo.INVALID_ID
- && !isCallerSessionOwner(mPackageInstaller, Process.myUid(), mStagedSessionId))) {
- return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
- }
-
- mIsTrustedSource = isInstallRequestFromTrustedSource(sourceInfo, mIntent, originatingUid);
-
- if (!isInstallPermissionGrantedOrRequested(mContext, mCallingUid, originatingUid,
- mIsTrustedSource)) {
- return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
- }
-
- String restriction = getDevicePolicyRestrictions();
- if (restriction != null) {
- InstallAborted.Builder abortedBuilder =
- new InstallAborted.Builder(ABORT_REASON_POLICY).setMessage(restriction);
- final Intent adminSupportDetailsIntent =
- mDevicePolicyManager.createAdminSupportIntent(restriction);
- if (adminSupportDetailsIntent != null) {
- abortedBuilder.setResultIntent(adminSupportDetailsIntent);
- }
- return abortedBuilder.build();
- }
-
- maybeRemoveInvalidInstallerPackageName(callerInfo);
-
- return new InstallStaging();
- }
-
- /**
- * @return the ApplicationInfo for the installation source (the calling package), if available
- */
- @Nullable
- private ApplicationInfo getSourceInfo(@Nullable String callingPackage) {
- if (callingPackage == null) {
- return null;
- }
- try {
- return mPackageManager.getApplicationInfo(callingPackage, 0);
- } catch (PackageManager.NameNotFoundException ignored) {
- return null;
- }
- }
-
- private boolean isInstallRequestFromTrustedSource(ApplicationInfo sourceInfo, Intent intent,
- int originatingUid) {
- boolean isNotUnknownSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false);
- return sourceInfo != null && sourceInfo.isPrivilegedApp()
- && (isNotUnknownSource
- || isPermissionGranted(mContext, Manifest.permission.INSTALL_PACKAGES, originatingUid));
- }
-
- private String getDevicePolicyRestrictions() {
- final String[] restrictions = new String[]{
- UserManager.DISALLOW_INSTALL_APPS,
- UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
- UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY
- };
-
- for (String restriction : restrictions) {
- if (!mUserManager.hasUserRestrictionForUser(restriction, Process.myUserHandle())) {
- continue;
- }
- return restriction;
- }
- return null;
- }
-
- private void maybeRemoveInvalidInstallerPackageName(CallerInfo callerInfo) {
- final String installerPackageNameFromIntent =
- mIntent.getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME);
- if (installerPackageNameFromIntent == null) {
- return;
- }
- if (!TextUtils.equals(installerPackageNameFromIntent, callerInfo.getPackageName())
- && !isPermissionGranted(mPackageManager, Manifest.permission.INSTALL_PACKAGES,
- callerInfo.getPackageName())) {
- Log.e(TAG, "The given installer package name " + installerPackageNameFromIntent
- + " is invalid. Remove it.");
- EventLog.writeEvent(0x534e4554, "236687884", callerInfo.getUid(),
- "Invalid EXTRA_INSTALLER_PACKAGE_NAME");
- mIntent.removeExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME);
- }
- }
-
- public void stageForInstall() {
- Uri uri = mIntent.getData();
- if (mStagedSessionId != SessionInfo.INVALID_ID
- || mIsSessionInstall
- || (uri != null && SCHEME_PACKAGE.equals(uri.getScheme()))) {
- // For a session based install or installing with a package:// URI, there is no file
- // for us to stage.
- mStagingResult.setValue(new InstallReady());
- return;
- }
- if (uri != null
- && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())
- && canPackageQuery(mContext, mCallingUid, uri)) {
-
- if (mStagedSessionId > 0) {
- final PackageInstaller.SessionInfo info =
- mPackageInstaller.getSessionInfo(mStagedSessionId);
- if (info == null || !info.isActive() || info.getResolvedBaseApkPath() == null) {
- Log.w(TAG, "Session " + mStagedSessionId + " in funky state; ignoring");
- if (info != null) {
- cleanupStagingSession();
- }
- mStagedSessionId = 0;
- }
- }
-
- // Session does not exist, or became invalid.
- if (mStagedSessionId <= 0) {
- // Create session here to be able to show error.
- try (final AssetFileDescriptor afd =
- mContext.getContentResolver().openAssetFileDescriptor(uri, "r")) {
- ParcelFileDescriptor pfd = afd != null ? afd.getParcelFileDescriptor() : null;
- PackageInstaller.SessionParams params =
- createSessionParams(mIntent, pfd, uri.toString());
- mStagedSessionId = mPackageInstaller.createSession(params);
- } catch (IOException e) {
- Log.w(TAG, "Failed to create a staging session", e);
- mStagingResult.setValue(
- new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR)
- .setResultIntent(new Intent().putExtra(Intent.EXTRA_INSTALL_RESULT,
- PackageManager.INSTALL_FAILED_INVALID_APK))
- .setActivityResultCode(Activity.RESULT_FIRST_USER)
- .build());
- return;
- }
- }
-
- SessionStageListener listener = new SessionStageListener() {
- @Override
- public void onStagingSuccess(SessionInfo info) {
- //TODO: Verify if the returned sessionInfo should be used anywhere
- mStagingResult.setValue(new InstallReady());
- }
-
- @Override
- public void onStagingFailure() {
- cleanupStagingSession();
- mStagingResult.setValue(
- new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR)
- .setResultIntent(new Intent().putExtra(Intent.EXTRA_INSTALL_RESULT,
- PackageManager.INSTALL_FAILED_INVALID_APK))
- .setActivityResultCode(Activity.RESULT_FIRST_USER)
- .build());
- }
- };
- if (mSessionStager != null) {
- mSessionStager.cancel(true);
- }
- mSessionStager = new SessionStager(mContext, uri, mStagedSessionId, listener);
- mSessionStager.execute();
- }
- }
-
- public int getStagedSessionId() {
- return mStagedSessionId;
- }
-
- private void cleanupStagingSession() {
- if (mStagedSessionId > 0) {
- try {
- mPackageInstaller.abandonSession(mStagedSessionId);
- } catch (SecurityException ignored) {
- }
- mStagedSessionId = 0;
- }
- }
-
- private PackageInstaller.SessionParams createSessionParams(@NonNull Intent intent,
- @Nullable ParcelFileDescriptor pfd, @NonNull String debugPathName) {
- PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
- PackageInstaller.SessionParams.MODE_FULL_INSTALL);
- final Uri referrerUri = intent.getParcelableExtra(Intent.EXTRA_REFERRER, Uri.class);
- params.setPackageSource(
- referrerUri != null ? PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE
- : PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE);
- params.setInstallAsInstantApp(false);
- params.setReferrerUri(referrerUri);
- params.setOriginatingUri(
- intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI, Uri.class));
- params.setOriginatingUid(intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID,
- Process.INVALID_UID));
- params.setInstallerPackageName(intent.getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME));
- params.setInstallReason(PackageManager.INSTALL_REASON_USER);
- // Disable full screen intent usage by for sideloads.
- params.setPermissionState(Manifest.permission.USE_FULL_SCREEN_INTENT,
- PackageInstaller.SessionParams.PERMISSION_STATE_DENIED);
-
- if (pfd != null) {
- try {
- final PackageInstaller.InstallInfo result = mPackageInstaller.readInstallInfo(pfd,
- debugPathName, 0);
- params.setAppPackageName(result.getPackageName());
- params.setInstallLocation(result.getInstallLocation());
- params.setSize(result.calculateInstalledSize(params, pfd));
- } catch (PackageInstaller.PackageParsingException e) {
- Log.e(TAG, "Cannot parse package " + debugPathName + ". Assuming defaults.", e);
- params.setSize(pfd.getStatSize());
- } catch (IOException e) {
- Log.e(TAG,
- "Cannot calculate installed size " + debugPathName
- + ". Try only apk size.", e);
- }
- } else {
- Log.e(TAG, "Cannot parse package " + debugPathName + ". Assuming defaults.");
- }
- return params;
- }
-
- /**
- * Processes Install session, file:// or package:// URI to generate data pertaining to user
- * confirmation for an install. This method also checks if the source app has the AppOp granted
- * to install unknown apps. If an AppOp is to be requested, cache the user action prompt data to
- * be reused once appOp has been granted
- *
- * @return <ul>
- * <li>InstallAborted </li>
- * <ul>
- * <li> If install session is invalid (not sealed or resolvedBaseApk path
- * is invalid) </li>
- * <li> Source app doesn't have visibility to target app </li>
- * <li> The APK is invalid </li>
- * <li> URI is invalid </li>
- * <li> Can't get ApplicationInfo for source app, to request AppOp </li>
- * </ul>
- * <li> InstallUserActionRequired</li>
- * <ul>
- * <li> If AppOP is granted and user action is required to proceed
- * with install </li>
- * <li> If AppOp grant is to be requested from the user</li>
- * </ul>
- * </ul>
- */
- public InstallStage requestUserConfirmation() {
- if (mIsTrustedSource) {
- if (mLocalLOGV) {
- Log.i(TAG, "install allowed");
- }
- // Returns InstallUserActionRequired stage if install details could be successfully
- // computed, else it returns InstallAborted.
- return generateConfirmationSnippet();
- } else {
- InstallStage unknownSourceStage = handleUnknownSources(mAppOpRequestInfo);
- if (unknownSourceStage.getStageCode() == InstallStage.STAGE_READY) {
- // Source app already has appOp granted.
- return generateConfirmationSnippet();
- } else {
- return unknownSourceStage;
- }
- }
- }
-
-
- private InstallStage generateConfirmationSnippet() {
- final Object packageSource;
- int pendingUserActionReason = -1;
- if (PackageInstaller.ACTION_CONFIRM_INSTALL.equals(mIntent.getAction())) {
- final SessionInfo info = mPackageInstaller.getSessionInfo(mSessionId);
- String resolvedPath = info != null ? info.getResolvedBaseApkPath() : null;
-
- if (info == null || !info.isSealed() || resolvedPath == null) {
- Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
- return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
- }
- packageSource = Uri.fromFile(new File(resolvedPath));
- // TODO: Not sure where is this used yet. PIA.java passes it to
- // InstallInstalling if not null
- // mOriginatingURI = null;
- // mReferrerURI = null;
- pendingUserActionReason = info.getPendingUserActionReason();
- } else if (PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL.equals(mIntent.getAction())) {
- final SessionInfo info = mPackageInstaller.getSessionInfo(mSessionId);
-
- if (info == null || !info.isPreApprovalRequested()) {
- Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
- return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
- }
- packageSource = info;
- // mOriginatingURI = null;
- // mReferrerURI = null;
- pendingUserActionReason = info.getPendingUserActionReason();
- } else {
- // Two possible origins:
- // 1. Installation with SCHEME_PACKAGE.
- // 2. Installation with "file://" for session created by this app
- if (mIntent.getData() != null && mIntent.getData().getScheme().equals(SCHEME_PACKAGE)) {
- packageSource = mIntent.getData();
- } else {
- SessionInfo stagedSessionInfo = mPackageInstaller.getSessionInfo(mStagedSessionId);
- packageSource = Uri.fromFile(new File(stagedSessionInfo.getResolvedBaseApkPath()));
- }
- // mOriginatingURI = mIntent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
- // mReferrerURI = mIntent.getParcelableExtra(Intent.EXTRA_REFERRER);
- pendingUserActionReason = PackageInstaller.REASON_CONFIRM_PACKAGE_CHANGE;
- }
-
- // if there's nothing to do, quietly slip into the ether
- if (packageSource == null) {
- Log.w(TAG, "Unspecified source");
- return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR)
- .setResultIntent(new Intent().putExtra(Intent.EXTRA_INSTALL_RESULT,
- PackageManager.INSTALL_FAILED_INVALID_URI))
- .setActivityResultCode(Activity.RESULT_FIRST_USER)
- .build();
- }
-
- return processAppSnippet(packageSource, pendingUserActionReason);
- }
-
- /**
- * Parse the Uri (post-commit install session) or use the SessionInfo (pre-commit install
- * session) to set up the installer for this install.
- *
- * @param source The source of package URI or SessionInfo
- * @return {@code true} iff the installer could be set up
- */
- private InstallStage processAppSnippet(Object source, int userActionReason) {
- if (source instanceof Uri) {
- return processPackageUri((Uri) source, userActionReason);
- } else if (source instanceof SessionInfo) {
- return processSessionInfo((SessionInfo) source, userActionReason);
- }
- return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
- }
-
- /**
- * Parse the Uri and set up the installer for this package.
- *
- * @param packageUri The URI to parse
- * @return {@code true} iff the installer could be set up
- */
- private InstallStage processPackageUri(final Uri packageUri, int userActionReason) {
- final String scheme = packageUri.getScheme();
- final String packageName = packageUri.getSchemeSpecificPart();
-
- if (scheme == null) {
- return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
- }
-
- if (mLocalLOGV) {
- Log.i(TAG, "processPackageUri(): uri = " + packageUri + ", scheme = " + scheme);
- }
-
- switch (scheme) {
- case SCHEME_PACKAGE -> {
- for (UserHandle handle : mUserManager.getUserHandles(true)) {
- PackageManager pmForUser = mContext.createContextAsUser(handle, 0)
- .getPackageManager();
- try {
- if (pmForUser.canPackageQuery(mCallingPackage, packageName)) {
- mNewPackageInfo = pmForUser.getPackageInfo(packageName,
- PackageManager.GET_PERMISSIONS
- | PackageManager.MATCH_UNINSTALLED_PACKAGES);
- }
- } catch (NameNotFoundException ignored) {
- }
- }
- if (mNewPackageInfo == null) {
- Log.w(TAG, "Requested package " + packageUri.getSchemeSpecificPart()
- + " not available. Discontinuing installation");
- return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR)
- .setErrorDialogType(DLG_PACKAGE_ERROR)
- .setResultIntent(new Intent().putExtra(Intent.EXTRA_INSTALL_RESULT,
- PackageManager.INSTALL_FAILED_INVALID_APK))
- .setActivityResultCode(Activity.RESULT_FIRST_USER)
- .build();
- }
- mAppSnippet = getAppSnippet(mContext, mNewPackageInfo);
- if (mLocalLOGV) {
- Log.i(TAG, "Created snippet for " + mAppSnippet.getLabel());
- }
- }
- case ContentResolver.SCHEME_FILE -> {
- File sourceFile = new File(packageUri.getPath());
- mNewPackageInfo = getPackageInfo(mContext, sourceFile,
- PackageManager.GET_PERMISSIONS);
-
- // Check for parse errors
- if (mNewPackageInfo == null) {
- Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
- return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR)
- .setErrorDialogType(DLG_PACKAGE_ERROR)
- .setResultIntent(new Intent().putExtra(Intent.EXTRA_INSTALL_RESULT,
- PackageManager.INSTALL_FAILED_INVALID_APK))
- .setActivityResultCode(Activity.RESULT_FIRST_USER)
- .build();
- }
- if (mLocalLOGV) {
- Log.i(TAG, "Creating snippet for local file " + sourceFile);
- }
- mAppSnippet = getAppSnippet(mContext, mNewPackageInfo.applicationInfo, sourceFile);
- }
- default -> {
- Log.e(TAG, "Unexpected URI scheme " + packageUri);
- return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
- }
- }
-
- return new InstallUserActionRequired.Builder(
- USER_ACTION_REASON_INSTALL_CONFIRMATION, mAppSnippet)
- .setDialogMessage(getUpdateMessage(mNewPackageInfo, userActionReason))
- .setAppUpdating(isAppUpdating(mNewPackageInfo))
- .build();
- }
-
- /**
- * Use the SessionInfo and set up the installer for pre-commit install session.
- *
- * @param sessionInfo The SessionInfo to compose
- * @return {@code true} iff the installer could be set up
- */
- private InstallStage processSessionInfo(@NonNull SessionInfo sessionInfo,
- int userActionReason) {
- mNewPackageInfo = generateStubPackageInfo(sessionInfo.getAppPackageName());
-
- mAppSnippet = getAppSnippet(mContext, sessionInfo);
- return new InstallUserActionRequired.Builder(
- USER_ACTION_REASON_INSTALL_CONFIRMATION, mAppSnippet)
- .setAppUpdating(isAppUpdating(mNewPackageInfo))
- .setDialogMessage(getUpdateMessage(mNewPackageInfo, userActionReason))
- .build();
- }
-
- private String getUpdateMessage(PackageInfo pkgInfo, int userActionReason) {
- if (isAppUpdating(pkgInfo)) {
- final CharSequence existingUpdateOwnerLabel = getExistingUpdateOwnerLabel(pkgInfo);
- final CharSequence requestedUpdateOwnerLabel = getApplicationLabel(mCallingPackage);
-
- if (!TextUtils.isEmpty(existingUpdateOwnerLabel)
- && userActionReason == PackageInstaller.REASON_REMIND_OWNERSHIP) {
- return mContext.getString(R.string.install_confirm_question_update_owner_reminder,
- requestedUpdateOwnerLabel, existingUpdateOwnerLabel);
- }
- }
- return null;
- }
-
- private CharSequence getExistingUpdateOwnerLabel(PackageInfo pkgInfo) {
- try {
- final String packageName = pkgInfo.packageName;
- final InstallSourceInfo sourceInfo = mPackageManager.getInstallSourceInfo(packageName);
- final String existingUpdateOwner = sourceInfo.getUpdateOwnerPackageName();
- return getApplicationLabel(existingUpdateOwner);
- } catch (NameNotFoundException e) {
- return null;
- }
- }
-
- private CharSequence getApplicationLabel(String packageName) {
- try {
- final ApplicationInfo appInfo = mPackageManager.getApplicationInfo(packageName,
- ApplicationInfoFlags.of(0));
- return mPackageManager.getApplicationLabel(appInfo);
- } catch (NameNotFoundException e) {
- return null;
- }
- }
-
- private boolean isAppUpdating(PackageInfo newPkgInfo) {
- String pkgName = newPkgInfo.packageName;
- // Check if there is already a package on the device with this name
- // but it has been renamed to something else.
- String[] oldName = mPackageManager.canonicalToCurrentPackageNames(new String[]{pkgName});
- if (oldName != null && oldName.length > 0 && oldName[0] != null) {
- pkgName = oldName[0];
- newPkgInfo.packageName = pkgName;
- newPkgInfo.applicationInfo.packageName = pkgName;
- }
- // Check if package is already installed. display confirmation dialog if replacing pkg
- try {
- // This is a little convoluted because we want to get all uninstalled
- // apps, but this may include apps with just data, and if it is just
- // data we still want to count it as "installed".
- ApplicationInfo appInfo = mPackageManager.getApplicationInfo(pkgName,
- PackageManager.MATCH_UNINSTALLED_PACKAGES);
- if ((appInfo.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
- return false;
- }
- } catch (NameNotFoundException e) {
- return false;
- }
- return true;
- }
-
- /**
- * Once the user returns from Settings related to installing from unknown sources, reattempt
- * the installation if the source app is granted permission to install other apps. Abort the
- * installation if the source app is still not granted installing permission.
- * @return {@link InstallUserActionRequired} containing data required to ask user confirmation
- * to proceed with the install.
- * {@link InstallAborted} if there was an error while recomputing, or the source still
- * doesn't have install permission.
- */
- public InstallStage reattemptInstall() {
- InstallStage unknownSourceStage = handleUnknownSources(mAppOpRequestInfo);
- if (unknownSourceStage.getStageCode() == InstallStage.STAGE_READY) {
- // Source app now has appOp granted.
- return generateConfirmationSnippet();
- } else if (unknownSourceStage.getStageCode() == InstallStage.STAGE_ABORTED) {
- // There was some error in determining the AppOp code for the source app.
- // Abort installation
- return unknownSourceStage;
- } else {
- // AppOpsManager again returned a MODE_ERRORED or MODE_DEFAULT op code. This was
- // unexpected while reattempting the install. Let's abort it.
- Log.e(TAG, "AppOp still not granted.");
- return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
- }
- }
-
- private InstallStage handleUnknownSources(AppOpRequestInfo requestInfo) {
- if (requestInfo.getCallingPackage() == null) {
- Log.i(TAG, "No source found for package " + mNewPackageInfo.packageName);
- return new InstallUserActionRequired.Builder(
- USER_ACTION_REASON_ANONYMOUS_SOURCE, null)
- .build();
- }
- // Shouldn't use static constant directly, see b/65534401.
- final String appOpStr =
- AppOpsManager.permissionToOp(Manifest.permission.REQUEST_INSTALL_PACKAGES);
- final int appOpMode = mAppOpsManager.noteOpNoThrow(appOpStr,
- requestInfo.getOriginatingUid(),
- requestInfo.getCallingPackage(), requestInfo.getAttributionTag(),
- "Started package installation activity");
-
- if (mLocalLOGV) {
- Log.i(TAG, "handleUnknownSources(): appMode=" + appOpMode);
- }
- switch (appOpMode) {
- case AppOpsManager.MODE_DEFAULT:
- mAppOpsManager.setMode(appOpStr, requestInfo.getOriginatingUid(),
- requestInfo.getCallingPackage(), AppOpsManager.MODE_ERRORED);
- // fall through
- case AppOpsManager.MODE_ERRORED:
- try {
- ApplicationInfo sourceInfo =
- mPackageManager.getApplicationInfo(requestInfo.getCallingPackage(), 0);
- AppSnippet sourceAppSnippet = getAppSnippet(mContext, sourceInfo);
- return new InstallUserActionRequired.Builder(
- USER_ACTION_REASON_UNKNOWN_SOURCE, sourceAppSnippet)
- .setDialogMessage(requestInfo.getCallingPackage())
- .build();
- } catch (NameNotFoundException e) {
- Log.e(TAG, "Did not find appInfo for " + requestInfo.getCallingPackage());
- return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
- }
- case AppOpsManager.MODE_ALLOWED:
- return new InstallReady();
- default:
- Log.e(TAG, "Invalid app op mode " + appOpMode
- + " for OP_REQUEST_INSTALL_PACKAGES found for uid "
- + requestInfo.getOriginatingUid());
- return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
- }
- }
-
-
- /**
- * Kick off the installation. Register a broadcast listener to get the result of the
- * installation and commit the staged session here. If the installation was session based,
- * signal the PackageInstaller that the user has granted permission to proceed with the install
- */
- public void initiateInstall() {
- if (mSessionId > 0) {
- mPackageInstaller.setPermissionsResult(mSessionId, true);
- mInstallResult.setValue(new InstallAborted.Builder(ABORT_REASON_DONE)
- .setActivityResultCode(Activity.RESULT_OK).build());
- return;
- }
-
- Uri uri = mIntent.getData();
- if (uri != null && SCHEME_PACKAGE.equals(uri.getScheme())) {
- try {
- mPackageManager.installExistingPackage(mNewPackageInfo.packageName);
- setStageBasedOnResult(PackageInstaller.STATUS_SUCCESS, -1, null, -1);
- } catch (PackageManager.NameNotFoundException e) {
- setStageBasedOnResult(PackageInstaller.STATUS_FAILURE,
- PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null, -1);
- }
- return;
- }
-
- if (mStagedSessionId <= 0) {
- // How did we even land here?
- Log.e(TAG, "Invalid local session and caller initiated session");
- mInstallResult.setValue(new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR)
- .build());
- return;
- }
-
- int installId;
- try {
- mInstallResult.setValue(new InstallInstalling(mAppSnippet));
- installId = InstallEventReceiver.addObserver(mContext,
- EventResultPersister.GENERATE_NEW_ID, this::setStageBasedOnResult);
- } catch (EventResultPersister.OutOfIdsException e) {
- setStageBasedOnResult(PackageInstaller.STATUS_FAILURE,
- PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null, -1);
- return;
- }
-
- Intent broadcastIntent = new Intent(BROADCAST_ACTION);
- broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- broadcastIntent.setPackage(mContext.getPackageName());
- broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, installId);
-
- PendingIntent pendingIntent = PendingIntent.getBroadcast(
- mContext, installId, broadcastIntent,
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
-
- try {
- PackageInstaller.Session session = mPackageInstaller.openSession(mStagedSessionId);
- session.commit(pendingIntent.getIntentSender());
- } catch (Exception e) {
- Log.e(TAG, "Session " + mStagedSessionId + " could not be opened.", e);
- mPackageInstaller.abandonSession(mStagedSessionId);
- setStageBasedOnResult(PackageInstaller.STATUS_FAILURE,
- PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null, -1);
- }
- }
-
- private void setStageBasedOnResult(int statusCode, int legacyStatus, String message,
- int serviceId) {
- if (statusCode == PackageInstaller.STATUS_SUCCESS) {
- boolean shouldReturnResult = mIntent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false);
-
- InstallSuccess.Builder successBuilder = new InstallSuccess.Builder(mAppSnippet)
- .setShouldReturnResult(shouldReturnResult);
- Intent resultIntent;
- if (shouldReturnResult) {
- resultIntent = new Intent()
- .putExtra(Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_SUCCEEDED);
- } else {
- resultIntent = mPackageManager
- .getLaunchIntentForPackage(mNewPackageInfo.packageName);
- }
- successBuilder.setResultIntent(resultIntent);
-
- mInstallResult.setValue(successBuilder.build());
- } else {
- mInstallResult.setValue(
- new InstallFailed(mAppSnippet, statusCode, legacyStatus, message));
- }
- }
-
- public MutableLiveData<InstallStage> getInstallResult() {
- return mInstallResult;
- }
-
- /**
- * Cleanup the staged session. Also signal the packageinstaller that an install session is to
- * be aborted
- */
- public void cleanupInstall() {
- if (mSessionId > 0) {
- mPackageInstaller.setPermissionsResult(mSessionId, false);
- } else if (mStagedSessionId > 0) {
- cleanupStagingSession();
- }
- }
-
- /**
- * When the identity of the install source could not be determined, user can skip checking the
- * source and directly proceed with the install.
- */
- public InstallStage forcedSkipSourceCheck() {
- return generateConfirmationSnippet();
- }
-
- public MutableLiveData<Integer> getStagingProgress() {
- if (mSessionStager != null) {
- return mSessionStager.getProgress();
- }
- return new MutableLiveData<>(0);
- }
-
- public MutableLiveData<InstallStage> getStagingResult() {
- return mStagingResult;
- }
-
- public interface SessionStageListener {
-
- void onStagingSuccess(SessionInfo info);
-
- void onStagingFailure();
- }
-
- public static class CallerInfo {
-
- private final String mPackageName;
- private final int mUid;
-
- public CallerInfo(String packageName, int uid) {
- mPackageName = packageName;
- mUid = uid;
- }
-
- public String getPackageName() {
- return mPackageName;
- }
-
- public int getUid() {
- return mUid;
- }
- }
-
- public static class AppOpRequestInfo {
-
- private String mCallingPackage;
- private String mAttributionTag;
- private int mOrginatingUid;
-
- public AppOpRequestInfo(String callingPackage, int orginatingUid, String attributionTag) {
- mCallingPackage = callingPackage;
- mOrginatingUid = orginatingUid;
- mAttributionTag = attributionTag;
- }
-
- public String getCallingPackage() {
- return mCallingPackage;
- }
-
- public String getAttributionTag() {
- return mAttributionTag;
- }
-
- public int getOriginatingUid() {
- return mOrginatingUid;
- }
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
new file mode 100644
index 000000000000..326e533df0d8
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
@@ -0,0 +1,867 @@
+/*
+ * 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.packageinstaller.v2.model
+
+import android.Manifest
+import android.app.Activity
+import android.app.AppOpsManager
+import android.app.PendingIntent
+import android.app.admin.DevicePolicyManager
+import android.content.ContentResolver
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInfo
+import android.content.pm.PackageInstaller
+import android.content.pm.PackageInstaller.SessionInfo
+import android.content.pm.PackageInstaller.SessionParams
+import android.content.pm.PackageManager
+import android.net.Uri
+import android.os.ParcelFileDescriptor
+import android.os.Process
+import android.os.UserManager
+import android.text.TextUtils
+import android.util.EventLog
+import android.util.Log
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import com.android.packageinstaller.R
+import com.android.packageinstaller.common.EventResultPersister
+import com.android.packageinstaller.common.EventResultPersister.OutOfIdsException
+import com.android.packageinstaller.common.InstallEventReceiver
+import com.android.packageinstaller.v2.model.InstallAborted.Companion.ABORT_REASON_DONE
+import com.android.packageinstaller.v2.model.InstallAborted.Companion.ABORT_REASON_INTERNAL_ERROR
+import com.android.packageinstaller.v2.model.InstallAborted.Companion.ABORT_REASON_POLICY
+import com.android.packageinstaller.v2.model.InstallAborted.Companion.DLG_PACKAGE_ERROR
+import com.android.packageinstaller.v2.model.InstallUserActionRequired.Companion.USER_ACTION_REASON_ANONYMOUS_SOURCE
+import com.android.packageinstaller.v2.model.InstallUserActionRequired.Companion.USER_ACTION_REASON_INSTALL_CONFIRMATION
+import com.android.packageinstaller.v2.model.InstallUserActionRequired.Companion.USER_ACTION_REASON_UNKNOWN_SOURCE
+import com.android.packageinstaller.v2.model.PackageUtil.canPackageQuery
+import com.android.packageinstaller.v2.model.PackageUtil.generateStubPackageInfo
+import com.android.packageinstaller.v2.model.PackageUtil.getAppSnippet
+import com.android.packageinstaller.v2.model.PackageUtil.getPackageInfo
+import com.android.packageinstaller.v2.model.PackageUtil.getPackageNameForUid
+import com.android.packageinstaller.v2.model.PackageUtil.isCallerSessionOwner
+import com.android.packageinstaller.v2.model.PackageUtil.isInstallPermissionGrantedOrRequested
+import com.android.packageinstaller.v2.model.PackageUtil.isPermissionGranted
+import java.io.File
+import java.io.IOException
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+
+class InstallRepository(private val context: Context) {
+
+ private val packageManager: PackageManager = context.packageManager
+ private val packageInstaller: PackageInstaller = packageManager.packageInstaller
+ private val userManager: UserManager? = context.getSystemService(UserManager::class.java)
+ private val devicePolicyManager: DevicePolicyManager? =
+ context.getSystemService(DevicePolicyManager::class.java)
+ private val appOpsManager: AppOpsManager? = context.getSystemService(AppOpsManager::class.java)
+ private val localLOGV = false
+ private var isSessionInstall = false
+ private var isTrustedSource = false
+ private val _stagingResult = MutableLiveData<InstallStage>()
+ val stagingResult: LiveData<InstallStage>
+ get() = _stagingResult
+ private val _installResult = MutableLiveData<InstallStage>()
+ val installResult: LiveData<InstallStage>
+ get() = _installResult
+
+ /**
+ * Session ID for a session created when caller uses PackageInstaller APIs
+ */
+ private var sessionId = SessionInfo.INVALID_ID
+
+ /**
+ * Session ID for a session created by this app
+ */
+ var stagedSessionId = SessionInfo.INVALID_ID
+ private set
+ private var callingUid = Process.INVALID_UID
+ private var callingPackage: String? = null
+ private var sessionStager: SessionStager? = null
+ private lateinit var intent: Intent
+ private lateinit var appOpRequestInfo: AppOpRequestInfo
+ private lateinit var appSnippet: PackageUtil.AppSnippet
+
+ /**
+ * PackageInfo of the app being installed on device.
+ */
+ private var newPackageInfo: PackageInfo? = null
+
+ /**
+ * Extracts information from the incoming install intent, checks caller's permission to install
+ * packages, verifies that the caller is the install session owner (in case of a session based
+ * install) and checks if the current user has restrictions set that prevent app installation,
+ *
+ * @param intent the incoming [Intent] object for installing a package
+ * @param callerInfo [CallerInfo] that holds the callingUid and callingPackageName
+ * @return
+ * * [InstallAborted] if there are errors while performing the checks
+ * * [InstallStaging] after successfully performing the checks
+ */
+ fun performPreInstallChecks(intent: Intent, callerInfo: CallerInfo): InstallStage {
+ this.intent = intent
+
+ var callingAttributionTag: String? = null
+
+ isSessionInstall =
+ PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL == intent.action
+ || PackageInstaller.ACTION_CONFIRM_INSTALL == intent.action
+
+ sessionId = if (isSessionInstall)
+ intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, SessionInfo.INVALID_ID)
+ else SessionInfo.INVALID_ID
+
+ stagedSessionId = intent.getIntExtra(EXTRA_STAGED_SESSION_ID, SessionInfo.INVALID_ID)
+
+ callingPackage = callerInfo.packageName
+
+ if (callingPackage == null && sessionId != SessionInfo.INVALID_ID) {
+ val sessionInfo: SessionInfo? = packageInstaller.getSessionInfo(sessionId)
+ callingPackage = sessionInfo?.getInstallerPackageName()
+ callingAttributionTag = sessionInfo?.getInstallerAttributionTag()
+ }
+
+ // Uid of the source package, coming from ActivityManager
+ callingUid = callerInfo.uid
+ if (callingUid == Process.INVALID_UID) {
+ Log.e(LOG_TAG, "Could not determine the launching uid.")
+ }
+ val sourceInfo: ApplicationInfo? = getSourceInfo(callingPackage)
+ // Uid of the source package, with a preference to uid from ApplicationInfo
+ val originatingUid = sourceInfo?.uid ?: callingUid
+ appOpRequestInfo = AppOpRequestInfo(
+ getPackageNameForUid(context, originatingUid, callingPackage),
+ originatingUid, callingAttributionTag
+ )
+
+ if (callingUid == Process.INVALID_UID && sourceInfo == null) {
+ // Caller's identity could not be determined. Abort the install
+ return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+ }
+
+ if ((sessionId != SessionInfo.INVALID_ID
+ && !isCallerSessionOwner(packageInstaller, originatingUid, sessionId))
+ || (stagedSessionId != SessionInfo.INVALID_ID
+ && !isCallerSessionOwner(packageInstaller, Process.myUid(), stagedSessionId))
+ ) {
+ return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+ }
+
+ isTrustedSource = isInstallRequestFromTrustedSource(sourceInfo, this.intent, originatingUid)
+ if (!isInstallPermissionGrantedOrRequested(
+ context, callingUid, originatingUid, isTrustedSource
+ )
+ ) {
+ return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+ }
+
+ val restriction = getDevicePolicyRestrictions()
+ if (restriction != null) {
+ val adminSupportDetailsIntent =
+ devicePolicyManager!!.createAdminSupportIntent(restriction)
+ return InstallAborted(
+ ABORT_REASON_POLICY, message = restriction, resultIntent = adminSupportDetailsIntent
+ )
+ }
+
+ maybeRemoveInvalidInstallerPackageName(callerInfo)
+
+ return InstallStaging()
+ }
+
+ /**
+ * @return the ApplicationInfo for the installation source (the calling package), if available
+ */
+ private fun getSourceInfo(callingPackage: String?): ApplicationInfo? {
+ return try {
+ callingPackage?.let { packageManager.getApplicationInfo(it, 0) }
+ } catch (ignored: PackageManager.NameNotFoundException) {
+ null
+ }
+ }
+
+ private fun isInstallRequestFromTrustedSource(
+ sourceInfo: ApplicationInfo?,
+ intent: Intent,
+ originatingUid: Int,
+ ): Boolean {
+ val isNotUnknownSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false)
+ return (sourceInfo != null && sourceInfo.isPrivilegedApp
+ && (isNotUnknownSource
+ || isPermissionGranted(context, Manifest.permission.INSTALL_PACKAGES, originatingUid)))
+ }
+
+ private fun getDevicePolicyRestrictions(): String? {
+ val restrictions = arrayOf(
+ UserManager.DISALLOW_INSTALL_APPS,
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY
+ )
+ for (restriction in restrictions) {
+ if (!userManager!!.hasUserRestrictionForUser(restriction, Process.myUserHandle())) {
+ continue
+ }
+ return restriction
+ }
+ return null
+ }
+
+ private fun maybeRemoveInvalidInstallerPackageName(callerInfo: CallerInfo) {
+ val installerPackageNameFromIntent =
+ intent.getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME) ?: return
+
+ if (!TextUtils.equals(installerPackageNameFromIntent, callerInfo.packageName)
+ && callerInfo.packageName != null
+ && isPermissionGranted(
+ packageManager, Manifest.permission.INSTALL_PACKAGES, callerInfo.packageName
+ )
+ ) {
+ Log.e(
+ LOG_TAG, "The given installer package name $installerPackageNameFromIntent"
+ + " is invalid. Remove it."
+ )
+ EventLog.writeEvent(
+ 0x534e4554, "236687884", callerInfo.uid,
+ "Invalid EXTRA_INSTALLER_PACKAGE_NAME"
+ )
+ intent.removeExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME)
+ }
+ }
+
+ @OptIn(DelicateCoroutinesApi::class)
+ fun stageForInstall() {
+ val uri = intent.data
+ if (stagedSessionId != SessionInfo.INVALID_ID
+ || isSessionInstall
+ || (uri != null && SCHEME_PACKAGE == uri.scheme)
+ ) {
+ // For a session based install or installing with a package:// URI, there is no file
+ // for us to stage.
+ _stagingResult.value = InstallReady()
+ return
+ }
+ if (uri != null
+ && ContentResolver.SCHEME_CONTENT == uri.scheme
+ && canPackageQuery(context, callingUid, uri)
+ ) {
+ if (stagedSessionId > 0) {
+ val info: SessionInfo? = packageInstaller.getSessionInfo(stagedSessionId)
+ if (info == null || !info.isActive || info.resolvedBaseApkPath == null) {
+ Log.w(LOG_TAG, "Session $stagedSessionId in funky state; ignoring")
+ if (info != null) {
+ cleanupStagingSession()
+ }
+ stagedSessionId = 0
+ }
+ }
+
+ // Session does not exist, or became invalid.
+ if (stagedSessionId <= 0) {
+ // Create session here to be able to show error.
+ try {
+ context.contentResolver.openAssetFileDescriptor(uri, "r").use { afd ->
+ val pfd: ParcelFileDescriptor? = afd?.parcelFileDescriptor
+ val params: SessionParams =
+ createSessionParams(intent, pfd, uri.toString())
+ stagedSessionId = packageInstaller.createSession(params)
+ }
+ } catch (e: IOException) {
+ Log.w(LOG_TAG, "Failed to create a staging session", e)
+ _stagingResult.value = InstallAborted(
+ ABORT_REASON_INTERNAL_ERROR,
+ resultIntent = Intent().putExtra(
+ Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_FAILED_INVALID_APK
+ ),
+ activityResultCode = Activity.RESULT_FIRST_USER
+ )
+ return
+ }
+ }
+
+ sessionStager = SessionStager(context, uri, stagedSessionId)
+ GlobalScope.launch(Dispatchers.Main) {
+ val wasFileStaged = sessionStager!!.execute()
+
+ if (wasFileStaged) {
+ _stagingResult.value = InstallReady()
+ } else {
+ cleanupStagingSession()
+ _stagingResult.value = InstallAborted(
+ ABORT_REASON_INTERNAL_ERROR,
+ resultIntent = Intent().putExtra(
+ Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_FAILED_INVALID_APK
+ ),
+ activityResultCode = Activity.RESULT_FIRST_USER
+ )
+ }
+ }
+ }
+ }
+
+ private fun cleanupStagingSession() {
+ if (stagedSessionId > 0) {
+ try {
+ packageInstaller.abandonSession(stagedSessionId)
+ } catch (ignored: SecurityException) {
+ }
+ stagedSessionId = 0
+ }
+ }
+
+ private fun createSessionParams(
+ intent: Intent,
+ pfd: ParcelFileDescriptor?,
+ debugPathName: String,
+ ): SessionParams {
+ val params = SessionParams(SessionParams.MODE_FULL_INSTALL)
+ val referrerUri = intent.getParcelableExtra(Intent.EXTRA_REFERRER, Uri::class.java)
+ params.setPackageSource(
+ if (referrerUri != null)
+ PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE
+ else PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE
+ )
+ params.setInstallAsInstantApp(false)
+ params.setReferrerUri(referrerUri)
+ params.setOriginatingUri(
+ intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI, Uri::class.java)
+ )
+ params.setOriginatingUid(
+ intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID, Process.INVALID_UID)
+ )
+ params.setInstallerPackageName(intent.getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME))
+ params.setInstallReason(PackageManager.INSTALL_REASON_USER)
+ // Disable full screen intent usage by for sideloads.
+ params.setPermissionState(
+ Manifest.permission.USE_FULL_SCREEN_INTENT, SessionParams.PERMISSION_STATE_DENIED
+ )
+ if (pfd != null) {
+ try {
+ val installInfo = packageInstaller.readInstallInfo(pfd, debugPathName, 0)
+ params.setAppPackageName(installInfo.packageName)
+ params.setInstallLocation(installInfo.installLocation)
+ params.setSize(installInfo.calculateInstalledSize(params, pfd))
+ } catch (e: PackageInstaller.PackageParsingException) {
+ Log.e(LOG_TAG, "Cannot parse package $debugPathName. Assuming defaults.", e)
+ params.setSize(pfd.statSize)
+ } catch (e: IOException) {
+ Log.e(LOG_TAG, "Cannot calculate installed size $debugPathName. " +
+ "Try only apk size.", e
+ )
+ }
+ } else {
+ Log.e(LOG_TAG, "Cannot parse package $debugPathName. Assuming defaults.")
+ }
+ return params
+ }
+
+ /**
+ * Processes Install session, file:// or package:// URI to generate data pertaining to user
+ * confirmation for an install. This method also checks if the source app has the AppOp granted
+ * to install unknown apps. If an AppOp is to be requested, cache the user action prompt data to
+ * be reused once appOp has been granted
+ *
+ * @return
+ * * [InstallAborted]
+ * * If install session is invalid (not sealed or resolvedBaseApk path is invalid)
+ * * Source app doesn't have visibility to target app
+ * * The APK is invalid
+ * * URI is invalid
+ * * Can't get ApplicationInfo for source app, to request AppOp
+ *
+ * * [InstallUserActionRequired]
+ * * If AppOP is granted and user action is required to proceed with install
+ * * If AppOp grant is to be requested from the user
+ */
+ fun requestUserConfirmation(): InstallStage {
+ return if (isTrustedSource) {
+ if (localLOGV) {
+ Log.i(LOG_TAG, "install allowed")
+ }
+ // Returns InstallUserActionRequired stage if install details could be successfully
+ // computed, else it returns InstallAborted.
+ generateConfirmationSnippet()
+ } else {
+ val unknownSourceStage = handleUnknownSources(appOpRequestInfo)
+ if (unknownSourceStage.stageCode == InstallStage.STAGE_READY) {
+ // Source app already has appOp granted.
+ generateConfirmationSnippet()
+ } else {
+ unknownSourceStage
+ }
+ }
+ }
+
+ private fun generateConfirmationSnippet(): InstallStage {
+ val packageSource: Any?
+ val pendingUserActionReason: Int
+
+ if (PackageInstaller.ACTION_CONFIRM_INSTALL == intent.action) {
+ val info = packageInstaller.getSessionInfo(sessionId)
+ val resolvedPath = info?.resolvedBaseApkPath
+ if (info == null || !info.isSealed || resolvedPath == null) {
+ Log.w(LOG_TAG, "Session $sessionId in funky state; ignoring")
+ return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+ }
+ packageSource = Uri.fromFile(File(resolvedPath))
+ // TODO: Not sure where is this used yet. PIA.java passes it to
+ // InstallInstalling if not null
+ // mOriginatingURI = null;
+ // mReferrerURI = null;
+ pendingUserActionReason = info.getPendingUserActionReason()
+ } else if (PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL == intent.action) {
+ val info = packageInstaller.getSessionInfo(sessionId)
+ if (info == null || !info.isPreApprovalRequested) {
+ Log.w(LOG_TAG, "Session $sessionId in funky state; ignoring")
+ return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+ }
+ packageSource = info
+ // mOriginatingURI = null;
+ // mReferrerURI = null;
+ pendingUserActionReason = info.getPendingUserActionReason()
+ } else {
+ // Two possible origins:
+ // 1. Installation with SCHEME_PACKAGE.
+ // 2. Installation with "file://" for session created by this app
+ packageSource =
+ if (intent.data?.scheme == SCHEME_PACKAGE) {
+ intent.data
+ } else {
+ val stagedSessionInfo = packageInstaller.getSessionInfo(stagedSessionId)
+ Uri.fromFile(File(stagedSessionInfo?.resolvedBaseApkPath!!))
+ }
+ // mOriginatingURI = mIntent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
+ // mReferrerURI = mIntent.getParcelableExtra(Intent.EXTRA_REFERRER);
+ pendingUserActionReason = PackageInstaller.REASON_CONFIRM_PACKAGE_CHANGE
+ }
+
+ // if there's nothing to do, quietly slip into the ether
+ if (packageSource == null) {
+ Log.w(LOG_TAG, "Unspecified source")
+ return InstallAborted(
+ ABORT_REASON_INTERNAL_ERROR,
+ resultIntent = Intent().putExtra(
+ Intent.EXTRA_INSTALL_RESULT,
+ PackageManager.INSTALL_FAILED_INVALID_URI
+ ),
+ activityResultCode = Activity.RESULT_FIRST_USER
+ )
+ }
+ return processAppSnippet(packageSource, pendingUserActionReason)
+ }
+
+ /**
+ * Parse the Uri (post-commit install session) or use the SessionInfo (pre-commit install
+ * session) to set up the installer for this install.
+ *
+ * @param source The source of package URI or SessionInfo
+ * @return
+ * * [InstallUserActionRequired] if source could be processed
+ * * [InstallAborted] if source is invalid or there was an error is processing a source
+ */
+ private fun processAppSnippet(source: Any, userActionReason: Int): InstallStage {
+ return when (source) {
+ is Uri -> processPackageUri(source, userActionReason)
+ is SessionInfo -> processSessionInfo(source, userActionReason)
+ else -> InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+ }
+ }
+
+ /**
+ * Parse the Uri and set up the installer for this package.
+ *
+ * @param packageUri The URI to parse
+ * @return
+ * * [InstallUserActionRequired] if source could be processed
+ * * [InstallAborted] if source is invalid or there was an error is processing a source
+ */
+ private fun processPackageUri(packageUri: Uri, userActionReason: Int): InstallStage {
+ val scheme = packageUri.scheme
+ val packageName = packageUri.schemeSpecificPart
+ if (scheme == null) {
+ return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+ }
+ if (localLOGV) {
+ Log.i(LOG_TAG, "processPackageUri(): uri = $packageUri, scheme = $scheme")
+ }
+ when (scheme) {
+ SCHEME_PACKAGE -> {
+ for (handle in userManager!!.getUserHandles(true)) {
+ val pmForUser = context.createContextAsUser(handle, 0).packageManager
+ try {
+ if (pmForUser.canPackageQuery(callingPackage!!, packageName)) {
+ newPackageInfo = pmForUser.getPackageInfo(
+ packageName,
+ PackageManager.GET_PERMISSIONS
+ or PackageManager.MATCH_UNINSTALLED_PACKAGES
+ )
+ }
+ } catch (ignored: PackageManager.NameNotFoundException) {
+ }
+ }
+ if (newPackageInfo == null) {
+ Log.w(
+ LOG_TAG, "Requested package " + packageUri.schemeSpecificPart
+ + " not available. Discontinuing installation"
+ )
+ return InstallAborted(
+ ABORT_REASON_INTERNAL_ERROR,
+ errorDialogType = DLG_PACKAGE_ERROR,
+ resultIntent = Intent().putExtra(
+ Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_FAILED_INVALID_APK
+ ),
+ activityResultCode = Activity.RESULT_FIRST_USER
+ )
+ }
+ appSnippet = getAppSnippet(context, newPackageInfo!!)
+ if (localLOGV) {
+ Log.i(LOG_TAG, "Created snippet for " + appSnippet.label)
+ }
+ }
+
+ ContentResolver.SCHEME_FILE -> {
+ val sourceFile = packageUri.path?.let { File(it) }
+ newPackageInfo = sourceFile?.let {
+ getPackageInfo(context, it, PackageManager.GET_PERMISSIONS)
+ }
+
+ // Check for parse errors
+ if (newPackageInfo == null) {
+ Log.w(
+ LOG_TAG, "Parse error when parsing manifest. " +
+ "Discontinuing installation"
+ )
+ return InstallAborted(
+ ABORT_REASON_INTERNAL_ERROR,
+ errorDialogType = DLG_PACKAGE_ERROR,
+ resultIntent = Intent().putExtra(
+ Intent.EXTRA_INSTALL_RESULT,
+ PackageManager.INSTALL_FAILED_INVALID_APK
+ ),
+ activityResultCode = Activity.RESULT_FIRST_USER
+ )
+ }
+ if (localLOGV) {
+ Log.i(LOG_TAG, "Creating snippet for local file $sourceFile")
+ }
+ appSnippet = getAppSnippet(context, newPackageInfo!!, sourceFile!!)
+ }
+
+ else -> {
+ Log.e(LOG_TAG, "Unexpected URI scheme $packageUri")
+ return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+ }
+ }
+ return InstallUserActionRequired(
+ USER_ACTION_REASON_INSTALL_CONFIRMATION, appSnippet, isAppUpdating(newPackageInfo!!),
+ getUpdateMessage(newPackageInfo!!, userActionReason)
+ )
+ }
+
+ /**
+ * Use the SessionInfo and set up the installer for pre-commit install session.
+ *
+ * @param sessionInfo The SessionInfo to compose
+ * @return
+ * * [InstallUserActionRequired] if source could be processed
+ * * [InstallAborted] if source is invalid or there was an error is processing a source
+ */
+ private fun processSessionInfo(sessionInfo: SessionInfo, userActionReason: Int): InstallStage {
+ newPackageInfo = generateStubPackageInfo(sessionInfo.getAppPackageName())
+ appSnippet = getAppSnippet(context, sessionInfo)
+
+ return InstallUserActionRequired(
+ USER_ACTION_REASON_INSTALL_CONFIRMATION, appSnippet, isAppUpdating(newPackageInfo!!),
+ getUpdateMessage(newPackageInfo!!, userActionReason)
+
+ )
+ }
+
+ private fun getUpdateMessage(pkgInfo: PackageInfo, userActionReason: Int): String? {
+ if (isAppUpdating(pkgInfo)) {
+ val existingUpdateOwnerLabel = getExistingUpdateOwnerLabel(pkgInfo)
+ val requestedUpdateOwnerLabel = getApplicationLabel(callingPackage)
+ if (!TextUtils.isEmpty(existingUpdateOwnerLabel)
+ && userActionReason == PackageInstaller.REASON_REMIND_OWNERSHIP
+ ) {
+ return context.getString(
+ R.string.install_confirm_question_update_owner_reminder,
+ requestedUpdateOwnerLabel, existingUpdateOwnerLabel
+ )
+ }
+ }
+ return null
+ }
+
+ private fun getExistingUpdateOwnerLabel(pkgInfo: PackageInfo): CharSequence? {
+ return try {
+ val packageName = pkgInfo.packageName
+ val sourceInfo = packageManager.getInstallSourceInfo(packageName)
+ val existingUpdateOwner = sourceInfo.updateOwnerPackageName
+ getApplicationLabel(existingUpdateOwner)
+ } catch (e: PackageManager.NameNotFoundException) {
+ null
+ }
+ }
+
+ private fun getApplicationLabel(packageName: String?): CharSequence? {
+ return try {
+ val appInfo = packageName?.let {
+ packageManager.getApplicationInfo(
+ it, PackageManager.ApplicationInfoFlags.of(0)
+ )
+ }
+ appInfo?.let { packageManager.getApplicationLabel(it) }
+ } catch (e: PackageManager.NameNotFoundException) {
+ null
+ }
+ }
+
+ private fun isAppUpdating(newPkgInfo: PackageInfo): Boolean {
+ var pkgName = newPkgInfo.packageName
+ // Check if there is already a package on the device with this name
+ // but it has been renamed to something else.
+ val oldName = packageManager.canonicalToCurrentPackageNames(arrayOf(pkgName))
+ if (oldName != null && oldName.isNotEmpty() && oldName[0] != null) {
+ pkgName = oldName[0]
+ newPkgInfo.packageName = pkgName
+ newPkgInfo.applicationInfo?.packageName = pkgName
+ }
+
+ // Check if package is already installed. display confirmation dialog if replacing pkg
+ try {
+ // This is a little convoluted because we want to get all uninstalled
+ // apps, but this may include apps with just data, and if it is just
+ // data we still want to count it as "installed".
+ val appInfo = packageManager.getApplicationInfo(
+ pkgName, PackageManager.MATCH_UNINSTALLED_PACKAGES
+ )
+ if (appInfo.flags and ApplicationInfo.FLAG_INSTALLED == 0) {
+ return false
+ }
+ } catch (e: PackageManager.NameNotFoundException) {
+ return false
+ }
+ return true
+ }
+
+ /**
+ * Once the user returns from Settings related to installing from unknown sources, reattempt
+ * the installation if the source app is granted permission to install other apps. Abort the
+ * installation if the source app is still not granted installing permission.
+ *
+ * @return
+ * * [InstallUserActionRequired] containing data required to ask user confirmation
+ * to proceed with the install.
+ * * [InstallAborted] if there was an error while recomputing, or the source still
+ * doesn't have install permission.
+ */
+ fun reattemptInstall(): InstallStage {
+ val unknownSourceStage = handleUnknownSources(appOpRequestInfo)
+ return when (unknownSourceStage.stageCode) {
+ InstallStage.STAGE_READY -> {
+ // Source app now has appOp granted.
+ generateConfirmationSnippet()
+ }
+
+ InstallStage.STAGE_ABORTED -> {
+ // There was some error in determining the AppOp code for the source app.
+ // Abort installation
+ unknownSourceStage
+ }
+
+ else -> {
+ // AppOpsManager again returned a MODE_ERRORED or MODE_DEFAULT op code. This was
+ // unexpected while reattempting the install. Let's abort it.
+ Log.e(LOG_TAG, "AppOp still not granted.")
+ InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+ }
+ }
+ }
+
+ private fun handleUnknownSources(requestInfo: AppOpRequestInfo): InstallStage {
+ if (requestInfo.callingPackage == null) {
+ Log.i(LOG_TAG, "No source found for package " + newPackageInfo?.packageName)
+ return InstallUserActionRequired(USER_ACTION_REASON_ANONYMOUS_SOURCE)
+ }
+ // Shouldn't use static constant directly, see b/65534401.
+ val appOpStr = AppOpsManager.permissionToOp(Manifest.permission.REQUEST_INSTALL_PACKAGES)
+ val appOpMode = appOpsManager!!.noteOpNoThrow(
+ appOpStr!!, requestInfo.originatingUid, requestInfo.callingPackage,
+ requestInfo.attributionTag, "Started package installation activity"
+ )
+ if (localLOGV) {
+ Log.i(LOG_TAG, "handleUnknownSources(): appMode=$appOpMode")
+ }
+
+ return when (appOpMode) {
+ AppOpsManager.MODE_DEFAULT, AppOpsManager.MODE_ERRORED -> {
+ if (appOpMode == AppOpsManager.MODE_DEFAULT) {
+ appOpsManager.setMode(
+ appOpStr, requestInfo.originatingUid, requestInfo.callingPackage,
+ AppOpsManager.MODE_ERRORED
+ )
+ }
+ try {
+ val sourceInfo =
+ packageManager.getApplicationInfo(requestInfo.callingPackage, 0)
+ val sourceAppSnippet = getAppSnippet(context, sourceInfo)
+ InstallUserActionRequired(
+ USER_ACTION_REASON_UNKNOWN_SOURCE, appSnippet = sourceAppSnippet,
+ dialogMessage = requestInfo.callingPackage
+ )
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.e(LOG_TAG, "Did not find appInfo for " + requestInfo.callingPackage)
+ InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+ }
+ }
+
+ AppOpsManager.MODE_ALLOWED -> InstallReady()
+
+ else -> {
+ Log.e(
+ LOG_TAG, "Invalid app op mode $appOpMode for " +
+ "OP_REQUEST_INSTALL_PACKAGES found for uid $requestInfo.originatingUid"
+ )
+ InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+ }
+ }
+ }
+
+ /**
+ * Kick off the installation. Register a broadcast listener to get the result of the
+ * installation and commit the staged session here. If the installation was session based,
+ * signal the PackageInstaller that the user has granted permission to proceed with the install
+ */
+ fun initiateInstall() {
+ if (sessionId > 0) {
+ packageInstaller.setPermissionsResult(sessionId, true)
+ _installResult.value = InstallAborted(
+ ABORT_REASON_DONE, activityResultCode = Activity.RESULT_OK
+ )
+ return
+ }
+ val uri = intent.data
+ if (SCHEME_PACKAGE == uri?.scheme) {
+ try {
+ packageManager.installExistingPackage(
+ newPackageInfo!!.packageName, PackageManager.INSTALL_REASON_USER
+ )
+ setStageBasedOnResult(PackageInstaller.STATUS_SUCCESS, -1, null)
+ } catch (e: PackageManager.NameNotFoundException) {
+ setStageBasedOnResult(
+ PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
+ null)
+ }
+ return
+ }
+ if (stagedSessionId <= 0) {
+ // How did we even land here?
+ Log.e(LOG_TAG, "Invalid local session and caller initiated session")
+ _installResult.value = InstallAborted(ABORT_REASON_INTERNAL_ERROR)
+ return
+ }
+ val installId: Int
+ try {
+ _installResult.value = InstallInstalling(appSnippet)
+ installId = InstallEventReceiver.addObserver(
+ context, EventResultPersister.GENERATE_NEW_ID
+ ) { statusCode: Int, legacyStatus: Int, message: String?, serviceId: Int ->
+ setStageBasedOnResult(statusCode, legacyStatus, message)
+ }
+ } catch (e: OutOfIdsException) {
+ setStageBasedOnResult(
+ PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null)
+ return
+ }
+ val broadcastIntent = Intent(BROADCAST_ACTION)
+ broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ broadcastIntent.setPackage(context.packageName)
+ broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, installId)
+ val pendingIntent = PendingIntent.getBroadcast(
+ context, installId, broadcastIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
+ )
+ try {
+ val session = packageInstaller.openSession(stagedSessionId)
+ session.commit(pendingIntent.intentSender)
+ } catch (e: Exception) {
+ Log.e(LOG_TAG, "Session $stagedSessionId could not be opened.", e)
+ packageInstaller.abandonSession(stagedSessionId)
+ setStageBasedOnResult(
+ PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null)
+ }
+ }
+
+ private fun setStageBasedOnResult(
+ statusCode: Int,
+ legacyStatus: Int,
+ message: String?
+ ) {
+ if (statusCode == PackageInstaller.STATUS_SUCCESS) {
+ val shouldReturnResult = intent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)
+ val resultIntent = if (shouldReturnResult) {
+ Intent().putExtra(Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_SUCCEEDED)
+ } else {
+ packageManager.getLaunchIntentForPackage(newPackageInfo!!.packageName)
+ }
+ _installResult.setValue(InstallSuccess(appSnippet, shouldReturnResult, resultIntent))
+ } else {
+ _installResult.setValue(InstallFailed(appSnippet, statusCode, legacyStatus, message))
+ }
+ }
+
+ /**
+ * Cleanup the staged session. Also signal the packageinstaller that an install session is to
+ * be aborted
+ */
+ fun cleanupInstall() {
+ if (sessionId > 0) {
+ packageInstaller.setPermissionsResult(sessionId, false)
+ } else if (stagedSessionId > 0) {
+ cleanupStagingSession()
+ }
+ }
+
+ /**
+ * When the identity of the install source could not be determined, user can skip checking the
+ * source and directly proceed with the install.
+ */
+ fun forcedSkipSourceCheck(): InstallStage {
+ return generateConfirmationSnippet()
+ }
+
+ val stagingProgress: LiveData<Int>
+ get() = sessionStager?.progress ?: MutableLiveData(0)
+
+ companion object {
+ const val EXTRA_STAGED_SESSION_ID = "com.android.packageinstaller.extra.STAGED_SESSION_ID"
+ const val SCHEME_PACKAGE = "package"
+ const val BROADCAST_ACTION = "com.android.packageinstaller.ACTION_INSTALL_COMMIT"
+ private val LOG_TAG = InstallRepository::class.java.simpleName
+ }
+
+ data class CallerInfo(val packageName: String?, val uid: Int)
+ data class AppOpRequestInfo(
+ val callingPackage: String?,
+ val originatingUid: Int,
+ val attributionTag: String?,
+ )
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt
new file mode 100644
index 000000000000..be49b39e9a48
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt
@@ -0,0 +1,134 @@
+/*
+ * 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
+ *
+ * https://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.packageinstaller.v2.model
+
+import android.app.Activity
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.graphics.drawable.Drawable
+
+sealed class InstallStage(val stageCode: Int) {
+
+ companion object {
+ const val STAGE_DEFAULT = -1
+ const val STAGE_ABORTED = 0
+ const val STAGE_STAGING = 1
+ const val STAGE_READY = 2
+ const val STAGE_USER_ACTION_REQUIRED = 3
+ const val STAGE_INSTALLING = 4
+ const val STAGE_SUCCESS = 5
+ const val STAGE_FAILED = 6
+ }
+}
+
+class InstallStaging : InstallStage(STAGE_STAGING)
+
+class InstallReady : InstallStage(STAGE_READY)
+
+data class InstallUserActionRequired(
+ val actionReason: Int,
+ private val appSnippet: PackageUtil.AppSnippet? = null,
+ val isAppUpdating: Boolean = false,
+ val dialogMessage: String? = null,
+) : InstallStage(STAGE_USER_ACTION_REQUIRED) {
+
+ val appIcon: Drawable?
+ get() = appSnippet?.icon
+
+ val appLabel: String?
+ get() = appSnippet?.let { appSnippet.label as String? }
+
+ companion object {
+ const val USER_ACTION_REASON_UNKNOWN_SOURCE = 0
+ const val USER_ACTION_REASON_ANONYMOUS_SOURCE = 1
+ const val USER_ACTION_REASON_INSTALL_CONFIRMATION = 2
+ }
+}
+
+data class InstallInstalling(private val appSnippet: PackageUtil.AppSnippet) :
+ InstallStage(STAGE_INSTALLING) {
+
+ val appIcon: Drawable?
+ get() = appSnippet.icon
+
+ val appLabel: String?
+ get() = appSnippet.label as String?
+}
+
+data class InstallSuccess(
+ private val appSnippet: PackageUtil.AppSnippet,
+ val shouldReturnResult: Boolean = false,
+ /**
+ *
+ * * If the caller is requesting a result back, this will hold the Intent with
+ * [Intent.EXTRA_INSTALL_RESULT] set to [PackageManager.INSTALL_SUCCEEDED] which is sent
+ * back to the caller.
+ *
+ * * If the caller doesn't want the result back, this will hold the Intent that launches
+ * the newly installed / updated app if a launchable activity exists.
+ */
+ val resultIntent: Intent? = null,
+) : InstallStage(STAGE_SUCCESS) {
+
+ val appIcon: Drawable?
+ get() = appSnippet.icon
+
+ val appLabel: String?
+ get() = appSnippet.label as String?
+}
+
+data class InstallFailed(
+ private val appSnippet: PackageUtil.AppSnippet,
+ val legacyCode: Int,
+ val statusCode: Int,
+ val message: String?,
+) : InstallStage(STAGE_FAILED) {
+
+ val appIcon: Drawable?
+ get() = appSnippet.icon
+
+ val appLabel: String?
+ get() = appSnippet.label as String?
+}
+
+data class InstallAborted(
+ val abortReason: Int,
+ /**
+ * It will hold the restriction name, when the restriction was enforced by the system, and not
+ * a device admin.
+ */
+ val message: String? = null,
+ /**
+ * * If abort reason is [ABORT_REASON_POLICY], then this will hold the Intent
+ * to display a support dialog when a feature was disabled by an admin. It will be
+ * `null` if the feature is disabled by the system. In this case, the restriction name
+ * will be set in [message]
+ * * If the abort reason is [ABORT_REASON_INTERNAL_ERROR], it **may** hold an
+ * intent to be sent as a result to the calling activity.
+ */
+ val resultIntent: Intent? = null,
+ val activityResultCode: Int = Activity.RESULT_CANCELED,
+ val errorDialogType: Int? = 0,
+) : InstallStage(STAGE_ABORTED) {
+
+ companion object {
+ const val ABORT_REASON_INTERNAL_ERROR = 0
+ const val ABORT_REASON_POLICY = 1
+ const val ABORT_REASON_DONE = 2
+ const val DLG_PACKAGE_ERROR = 1
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.java
deleted file mode 100644
index fe05237bdc57..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.java
+++ /dev/null
@@ -1,462 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.model;
-
-import android.Manifest;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageInstaller.SessionInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ProviderInfo;
-import android.content.res.Resources;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Process;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.Log;
-import androidx.annotation.NonNull;
-import java.io.File;
-import java.util.Arrays;
-import java.util.Objects;
-
-public class PackageUtil {
-
- private static final String TAG = InstallRepository.class.getSimpleName();
- private static final String DOWNLOADS_AUTHORITY = "downloads";
- private static final String SPLIT_BASE_APK_END_WITH = "base.apk";
-
- /**
- * Determines if the UID belongs to the system downloads provider and returns the
- * {@link ApplicationInfo} of the provider
- *
- * @param uid UID of the caller
- * @return {@link ApplicationInfo} of the provider if a downloads provider exists, it is a
- * system app, and its UID matches with the passed UID, null otherwise.
- */
- public static ApplicationInfo getSystemDownloadsProviderInfo(PackageManager pm, int uid) {
- final ProviderInfo providerInfo = pm.resolveContentProvider(
- DOWNLOADS_AUTHORITY, 0);
- if (providerInfo == null) {
- // There seems to be no currently enabled downloads provider on the system.
- return null;
- }
- ApplicationInfo appInfo = providerInfo.applicationInfo;
- if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 && uid == appInfo.uid) {
- return appInfo;
- }
- return null;
- }
-
- /**
- * Get the maximum target sdk for a UID.
- *
- * @param context The context to use
- * @param uid The UID requesting the install/uninstall
- * @return The maximum target SDK or -1 if the uid does not match any packages.
- */
- public static int getMaxTargetSdkVersionForUid(@NonNull Context context, int uid) {
- PackageManager pm = context.getPackageManager();
- final String[] packages = pm.getPackagesForUid(uid);
- int targetSdkVersion = -1;
- if (packages != null) {
- for (String packageName : packages) {
- try {
- ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
- targetSdkVersion = Math.max(targetSdkVersion, info.targetSdkVersion);
- } catch (PackageManager.NameNotFoundException e) {
- // Ignore and try the next package
- }
- }
- }
- return targetSdkVersion;
- }
-
- public static boolean canPackageQuery(Context context, int callingUid, Uri packageUri) {
- PackageManager pm = context.getPackageManager();
- ProviderInfo info = pm.resolveContentProvider(packageUri.getAuthority(),
- PackageManager.ComponentInfoFlags.of(0));
- if (info == null) {
- return false;
- }
- String targetPackage = info.packageName;
-
- String[] callingPackages = pm.getPackagesForUid(callingUid);
- if (callingPackages == null) {
- return false;
- }
- for (String callingPackage : callingPackages) {
- try {
- if (pm.canPackageQuery(callingPackage, targetPackage)) {
- return true;
- }
- } catch (PackageManager.NameNotFoundException e) {
- // no-op
- }
- }
- return false;
- }
-
- /**
- * @param context the {@link Context} object
- * @param permission the permission name to check
- * @param callingUid the UID of the caller who's permission is being checked
- * @return {@code true} if the callingUid is granted the said permission
- */
- public static boolean isPermissionGranted(Context context, String permission, int callingUid) {
- return context.checkPermission(permission, -1, callingUid)
- == PackageManager.PERMISSION_GRANTED;
- }
-
- /**
- * @param pm the {@link PackageManager} object
- * @param permission the permission name to check
- * @param packageName the name of the package who's permission is being checked
- * @return {@code true} if the package is granted the said permission
- */
- public static boolean isPermissionGranted(PackageManager pm, String permission,
- String packageName) {
- return pm.checkPermission(permission, packageName) == PackageManager.PERMISSION_GRANTED;
- }
-
- /**
- * @param context the {@link Context} object
- * @param callingUid the UID of the caller who's permission is being checked
- * @param originatingUid the UID from where install is being originated. This could be same as
- * callingUid or it will be the UID of the package performing a session based install
- * @param isTrustedSource whether install request is coming from a privileged app or an app that
- * has {@link Manifest.permission.INSTALL_PACKAGES} permission granted
- * @return {@code true} if the package is granted the said permission
- */
- public static boolean isInstallPermissionGrantedOrRequested(Context context, int callingUid,
- int originatingUid, boolean isTrustedSource) {
- boolean isDocumentsManager =
- isPermissionGranted(context, Manifest.permission.MANAGE_DOCUMENTS, callingUid);
- boolean isSystemDownloadsProvider =
- getSystemDownloadsProviderInfo(context.getPackageManager(), callingUid) != null;
-
- if (!isTrustedSource && !isSystemDownloadsProvider && !isDocumentsManager) {
-
- final int targetSdkVersion = getMaxTargetSdkVersionForUid(context, originatingUid);
- if (targetSdkVersion < 0) {
- // Invalid originating uid supplied. Abort install.
- Log.w(TAG, "Cannot get target sdk version for uid " + originatingUid);
- return false;
- } else if (targetSdkVersion >= Build.VERSION_CODES.O
- && !isUidRequestingPermission(context.getPackageManager(), originatingUid,
- Manifest.permission.REQUEST_INSTALL_PACKAGES)) {
- Log.e(TAG, "Requesting uid " + originatingUid + " needs to declare permission "
- + Manifest.permission.REQUEST_INSTALL_PACKAGES);
- return false;
- }
- }
- return true;
- }
-
- /**
- * @param pm the {@link PackageManager} object
- * @param uid the UID of the caller who's permission is being checked
- * @param permission the permission name to check
- * @return {@code true} if the caller is requesting the said permission in its Manifest
- */
- public static boolean isUidRequestingPermission(PackageManager pm, int uid, String permission) {
- final String[] packageNames = pm.getPackagesForUid(uid);
- if (packageNames == null) {
- return false;
- }
- for (final String packageName : packageNames) {
- final PackageInfo packageInfo;
- try {
- packageInfo = pm.getPackageInfo(packageName,
- PackageManager.GET_PERMISSIONS);
- } catch (PackageManager.NameNotFoundException e) {
- // Ignore and try the next package
- continue;
- }
- if (packageInfo.requestedPermissions != null
- && Arrays.asList(packageInfo.requestedPermissions).contains(permission)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * @param pi the {@link PackageInstaller} object to use
- * @param originatingUid the UID of the package performing a session based install
- * @param sessionId ID of the install session
- * @return {@code true} if the caller is the session owner
- */
- public static boolean isCallerSessionOwner(PackageInstaller pi, int originatingUid,
- int sessionId) {
- if (originatingUid == Process.ROOT_UID) {
- return true;
- }
- PackageInstaller.SessionInfo sessionInfo = pi.getSessionInfo(sessionId);
- if (sessionInfo == null) {
- return false;
- }
- int installerUid = sessionInfo.getInstallerUid();
- return originatingUid == installerUid;
- }
-
- /**
- * Generates a stub {@link PackageInfo} object for the given packageName
- */
- public static PackageInfo generateStubPackageInfo(String packageName) {
- final PackageInfo info = new PackageInfo();
- final ApplicationInfo aInfo = new ApplicationInfo();
- info.applicationInfo = aInfo;
- info.packageName = info.applicationInfo.packageName = packageName;
- return info;
- }
-
- /**
- * Generates an {@link AppSnippet} containing an appIcon and appLabel from the
- * {@link SessionInfo} object
- */
- public static AppSnippet getAppSnippet(Context context, SessionInfo info) {
- PackageManager pm = context.getPackageManager();
- CharSequence label = info.getAppLabel();
- Drawable icon = info.getAppIcon() != null ?
- new BitmapDrawable(context.getResources(), info.getAppIcon())
- : pm.getDefaultActivityIcon();
- return new AppSnippet(label, icon);
- }
-
- /**
- * Generates an {@link AppSnippet} containing an appIcon and appLabel from the
- * {@link PackageInfo} object
- */
- public static AppSnippet getAppSnippet(Context context, PackageInfo pkgInfo) {
- return getAppSnippet(context, pkgInfo.applicationInfo);
- }
-
- /**
- * Generates an {@link AppSnippet} containing an appIcon and appLabel from the
- * {@link ApplicationInfo} object
- */
- public static AppSnippet getAppSnippet(Context context, ApplicationInfo appInfo) {
- PackageManager pm = context.getPackageManager();
- CharSequence label = pm.getApplicationLabel(appInfo);
- Drawable icon = pm.getApplicationIcon(appInfo);
- return new AppSnippet(label, icon);
- }
-
- /**
- * Generates an {@link AppSnippet} containing an appIcon and appLabel from the
- * supplied APK file
- */
- public static AppSnippet getAppSnippet(Context context, ApplicationInfo appInfo,
- File sourceFile) {
- ApplicationInfo appInfoFromFile = processAppInfoForFile(appInfo, sourceFile);
- CharSequence label = getAppLabelFromFile(context, appInfoFromFile);
- Drawable icon = getAppIconFromFile(context, appInfoFromFile);
- return new AppSnippet(label, icon);
- }
-
- /**
- * Utility method to load application label
- *
- * @param context context of package that can load the resources
- * @param appInfo ApplicationInfo object of package whose resources are to be loaded
- */
- public static CharSequence getAppLabelFromFile(Context context, ApplicationInfo appInfo) {
- PackageManager pm = context.getPackageManager();
- CharSequence label = null;
- // Try to load the label from the package's resources. If an app has not explicitly
- // specified any label, just use the package name.
- if (appInfo.labelRes != 0) {
- try {
- label = appInfo.loadLabel(pm);
- } catch (Resources.NotFoundException e) {
- }
- }
- if (label == null) {
- label = (appInfo.nonLocalizedLabel != null) ?
- appInfo.nonLocalizedLabel : appInfo.packageName;
- }
- return label;
- }
-
- /**
- * Utility method to load application icon
- *
- * @param context context of package that can load the resources
- * @param appInfo ApplicationInfo object of package whose resources are to be loaded
- */
- public static Drawable getAppIconFromFile(Context context, ApplicationInfo appInfo) {
- PackageManager pm = context.getPackageManager();
- Drawable icon = null;
- // Try to load the icon from the package's resources. If an app has not explicitly
- // specified any resource, just use the default icon for now.
- try {
- if (appInfo.icon != 0) {
- try {
- icon = appInfo.loadIcon(pm);
- } catch (Resources.NotFoundException e) {
- }
- }
- if (icon == null) {
- icon = context.getPackageManager().getDefaultActivityIcon();
- }
- } catch (OutOfMemoryError e) {
- Log.i(TAG, "Could not load app icon", e);
- }
- return icon;
- }
-
- private static ApplicationInfo processAppInfoForFile(ApplicationInfo appInfo, File sourceFile) {
- final String archiveFilePath = sourceFile.getAbsolutePath();
- appInfo.publicSourceDir = archiveFilePath;
-
- if (appInfo.splitNames != null && appInfo.splitSourceDirs == null) {
- final File[] files = sourceFile.getParentFile().listFiles();
- final String[] splits = Arrays.stream(appInfo.splitNames)
- .map(i -> findFilePath(files, i + ".apk"))
- .filter(Objects::nonNull)
- .toArray(String[]::new);
-
- appInfo.splitSourceDirs = splits;
- appInfo.splitPublicSourceDirs = splits;
- }
- return appInfo;
- }
-
- private static String findFilePath(File[] files, String postfix) {
- for (File file : files) {
- final String path = file.getAbsolutePath();
- if (path.endsWith(postfix)) {
- return path;
- }
- }
- return null;
- }
-
- /**
- * @return the packageName corresponding to a UID.
- */
- public static String getPackageNameForUid(Context context, int sourceUid,
- String callingPackage) {
- if (sourceUid == Process.INVALID_UID) {
- return null;
- }
- // If the sourceUid belongs to the system downloads provider, we explicitly return the
- // name of the Download Manager package. This is because its UID is shared with multiple
- // packages, resulting in uncertainty about which package will end up first in the list
- // of packages associated with this UID
- PackageManager pm = context.getPackageManager();
- ApplicationInfo systemDownloadProviderInfo = getSystemDownloadsProviderInfo(
- pm, sourceUid);
- if (systemDownloadProviderInfo != null) {
- return systemDownloadProviderInfo.packageName;
- }
- String[] packagesForUid = pm.getPackagesForUid(sourceUid);
- if (packagesForUid == null) {
- return null;
- }
- if (packagesForUid.length > 1) {
- if (callingPackage != null) {
- for (String packageName : packagesForUid) {
- if (packageName.equals(callingPackage)) {
- return packageName;
- }
- }
- }
- Log.i(TAG, "Multiple packages found for source uid " + sourceUid);
- }
- return packagesForUid[0];
- }
-
- /**
- * Utility method to get package information for a given {@link File}
- */
- public static PackageInfo getPackageInfo(Context context, File sourceFile, int flags) {
- String filePath = sourceFile.getAbsolutePath();
- if (filePath.endsWith(SPLIT_BASE_APK_END_WITH)) {
- File dir = sourceFile.getParentFile();
- if (dir.listFiles().length > 1) {
- // split apks, use file directory to get archive info
- filePath = dir.getPath();
- }
- }
- try {
- return context.getPackageManager().getPackageArchiveInfo(filePath, flags);
- } catch (Exception ignored) {
- return null;
- }
- }
-
- /**
- * Is a profile part of a user?
- *
- * @param userManager The user manager
- * @param userHandle The handle of the user
- * @param profileHandle The handle of the profile
- *
- * @return If the profile is part of the user or the profile parent of the user
- */
- public static boolean isProfileOfOrSame(UserManager userManager, UserHandle userHandle,
- UserHandle profileHandle) {
- if (userHandle.equals(profileHandle)) {
- return true;
- }
- return userManager.getProfileParent(profileHandle) != null
- && userManager.getProfileParent(profileHandle).equals(userHandle);
- }
-
- /**
- * The class to hold an incoming package's icon and label.
- * See {@link #getAppSnippet(Context, SessionInfo)},
- * {@link #getAppSnippet(Context, PackageInfo)},
- * {@link #getAppSnippet(Context, ApplicationInfo)},
- * {@link #getAppSnippet(Context, ApplicationInfo, File)}
- */
- public static class AppSnippet {
-
- private CharSequence mLabel;
- private Drawable mIcon;
-
- public AppSnippet(CharSequence label, Drawable icon) {
- mLabel = label;
- mIcon = icon;
- }
-
- public AppSnippet() {
- }
-
- public CharSequence getLabel() {
- return mLabel;
- }
-
- public void setLabel(CharSequence mLabel) {
- this.mLabel = mLabel;
- }
-
- public Drawable getIcon() {
- return mIcon;
- }
-
- public void setIcon(Drawable mIcon) {
- this.mIcon = mIcon;
- }
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt
new file mode 100644
index 000000000000..8d8c2f1d8171
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt
@@ -0,0 +1,440 @@
+/*
+ * 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.packageinstaller.v2.model
+
+import android.Manifest
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInfo
+import android.content.pm.PackageInstaller
+import android.content.pm.PackageManager
+import android.content.res.Resources
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import android.net.Uri
+import android.os.Build
+import android.os.Process
+import android.os.UserHandle
+import android.os.UserManager
+import android.util.Log
+import java.io.File
+
+object PackageUtil {
+ private val LOG_TAG = InstallRepository::class.java.simpleName
+ private const val DOWNLOADS_AUTHORITY = "downloads"
+ private const val SPLIT_BASE_APK_END_WITH = "base.apk"
+
+ /**
+ * Determines if the UID belongs to the system downloads provider and returns the
+ * [ApplicationInfo] of the provider
+ *
+ * @param uid UID of the caller
+ * @return [ApplicationInfo] of the provider if a downloads provider exists, it is a
+ * system app, and its UID matches with the passed UID, null otherwise.
+ */
+ private fun getSystemDownloadsProviderInfo(pm: PackageManager, uid: Int): ApplicationInfo? {
+ // Check if there are currently enabled downloads provider on the system.
+ val providerInfo = pm.resolveContentProvider(DOWNLOADS_AUTHORITY, 0)
+ ?: return null
+ val appInfo = providerInfo.applicationInfo
+ return if ((appInfo.flags and ApplicationInfo.FLAG_SYSTEM != 0) && uid == appInfo.uid) {
+ appInfo
+ } else null
+ }
+
+ /**
+ * Get the maximum target sdk for a UID.
+ *
+ * @param context The context to use
+ * @param uid The UID requesting the install/uninstall
+ * @return The maximum target SDK or -1 if the uid does not match any packages.
+ */
+ @JvmStatic
+ fun getMaxTargetSdkVersionForUid(context: Context, uid: Int): Int {
+ val pm = context.packageManager
+ val packages = pm.getPackagesForUid(uid)
+ var targetSdkVersion = -1
+ if (packages != null) {
+ for (packageName in packages) {
+ try {
+ val info = pm.getApplicationInfo(packageName!!, 0)
+ targetSdkVersion = maxOf(targetSdkVersion, info.targetSdkVersion)
+ } catch (e: PackageManager.NameNotFoundException) {
+ // Ignore and try the next package
+ }
+ }
+ }
+ return targetSdkVersion
+ }
+
+ @JvmStatic
+ fun canPackageQuery(context: Context, callingUid: Int, packageUri: Uri): Boolean {
+ val pm = context.packageManager
+ val info = pm.resolveContentProvider(
+ packageUri.authority!!,
+ PackageManager.ComponentInfoFlags.of(0)
+ ) ?: return false
+ val targetPackage = info.packageName
+ val callingPackages = pm.getPackagesForUid(callingUid) ?: return false
+ for (callingPackage in callingPackages) {
+ try {
+ if (pm.canPackageQuery(callingPackage!!, targetPackage)) {
+ return true
+ }
+ } catch (e: PackageManager.NameNotFoundException) {
+ // no-op
+ }
+ }
+ return false
+ }
+
+ /**
+ * @param context the [Context] object
+ * @param permission the permission name to check
+ * @param callingUid the UID of the caller who's permission is being checked
+ * @return `true` if the callingUid is granted the said permission
+ */
+ @JvmStatic
+ fun isPermissionGranted(context: Context, permission: String, callingUid: Int): Boolean {
+ return (context.checkPermission(permission, -1, callingUid)
+ == PackageManager.PERMISSION_GRANTED)
+ }
+
+ /**
+ * @param pm the [PackageManager] object
+ * @param permission the permission name to check
+ * @param packageName the name of the package who's permission is being checked
+ * @return `true` if the package is granted the said permission
+ */
+ @JvmStatic
+ fun isPermissionGranted(pm: PackageManager, permission: String, packageName: String): Boolean {
+ return pm.checkPermission(permission, packageName) == PackageManager.PERMISSION_GRANTED
+ }
+
+ /**
+ * @param context the [Context] object
+ * @param callingUid the UID of the caller who's permission is being checked
+ * @param originatingUid the UID from where install is being originated. This could be same as
+ * callingUid or it will be the UID of the package performing a session based install
+ * @param isTrustedSource whether install request is coming from a privileged app or an app that
+ * has [Manifest.permission.INSTALL_PACKAGES] permission granted
+ * @return `true` if the package is granted the said permission
+ */
+ @JvmStatic
+ fun isInstallPermissionGrantedOrRequested(
+ context: Context,
+ callingUid: Int,
+ originatingUid: Int,
+ isTrustedSource: Boolean,
+ ): Boolean {
+ val isDocumentsManager =
+ isPermissionGranted(context, Manifest.permission.MANAGE_DOCUMENTS, callingUid)
+ val isSystemDownloadsProvider =
+ getSystemDownloadsProviderInfo(context.packageManager, callingUid) != null
+
+ if (!isTrustedSource && !isSystemDownloadsProvider && !isDocumentsManager) {
+ val targetSdkVersion = getMaxTargetSdkVersionForUid(context, originatingUid)
+ if (targetSdkVersion < 0) {
+ // Invalid originating uid supplied. Abort install.
+ Log.w(LOG_TAG, "Cannot get target sdk version for uid $originatingUid")
+ return false
+ } else if (targetSdkVersion >= Build.VERSION_CODES.O
+ && !isUidRequestingPermission(
+ context.packageManager, originatingUid,
+ Manifest.permission.REQUEST_INSTALL_PACKAGES
+ )
+ ) {
+ Log.e(
+ LOG_TAG, "Requesting uid " + originatingUid + " needs to declare permission "
+ + Manifest.permission.REQUEST_INSTALL_PACKAGES
+ )
+ return false
+ }
+ }
+ return true
+ }
+
+ /**
+ * @param pm the [PackageManager] object
+ * @param uid the UID of the caller who's permission is being checked
+ * @param permission the permission name to check
+ * @return `true` if the caller is requesting the said permission in its Manifest
+ */
+ private fun isUidRequestingPermission(
+ pm: PackageManager,
+ uid: Int,
+ permission: String,
+ ): Boolean {
+ val packageNames = pm.getPackagesForUid(uid) ?: return false
+ for (packageName in packageNames) {
+ val packageInfo: PackageInfo = try {
+ pm.getPackageInfo(packageName!!, PackageManager.GET_PERMISSIONS)
+ } catch (e: PackageManager.NameNotFoundException) {
+ // Ignore and try the next package
+ continue
+ }
+ if (packageInfo.requestedPermissions != null
+ && listOf(*packageInfo.requestedPermissions!!).contains(permission)
+ ) {
+ return true
+ }
+ }
+ return false
+ }
+
+ /**
+ * @param pi the [PackageInstaller] object to use
+ * @param originatingUid the UID of the package performing a session based install
+ * @param sessionId ID of the install session
+ * @return `true` if the caller is the session owner
+ */
+ @JvmStatic
+ fun isCallerSessionOwner(pi: PackageInstaller, originatingUid: Int, sessionId: Int): Boolean {
+ if (originatingUid == Process.ROOT_UID) {
+ return true
+ }
+ val sessionInfo = pi.getSessionInfo(sessionId) ?: return false
+ val installerUid = sessionInfo.getInstallerUid()
+ return originatingUid == installerUid
+ }
+
+ /**
+ * Generates a stub [PackageInfo] object for the given packageName
+ */
+ @JvmStatic
+ fun generateStubPackageInfo(packageName: String?): PackageInfo {
+ val info = PackageInfo()
+ val aInfo = ApplicationInfo()
+ info.applicationInfo = aInfo
+ info.applicationInfo!!.packageName = packageName
+ info.packageName = info.applicationInfo!!.packageName
+ return info
+ }
+
+ /**
+ * Generates an [AppSnippet] containing an appIcon and appLabel from the
+ * [PackageInstaller.SessionInfo] object
+ */
+ @JvmStatic
+ fun getAppSnippet(context: Context, info: PackageInstaller.SessionInfo): AppSnippet {
+ val pm = context.packageManager
+ val label = info.getAppLabel()
+ val icon = if (info.getAppIcon() != null) BitmapDrawable(
+ context.resources,
+ info.getAppIcon()
+ ) else pm.defaultActivityIcon
+ return AppSnippet(label, icon)
+ }
+
+ /**
+ * Generates an [AppSnippet] containing an appIcon and appLabel from the
+ * [PackageInfo] object
+ */
+ @JvmStatic
+ fun getAppSnippet(context: Context, pkgInfo: PackageInfo): AppSnippet {
+ return pkgInfo.applicationInfo?.let { getAppSnippet(context, it) } ?: run {
+ AppSnippet(pkgInfo.packageName, context.packageManager.defaultActivityIcon)
+ }
+ }
+
+ /**
+ * Generates an [AppSnippet] containing an appIcon and appLabel from the
+ * [ApplicationInfo] object
+ */
+ @JvmStatic
+ fun getAppSnippet(context: Context, appInfo: ApplicationInfo): AppSnippet {
+ val pm = context.packageManager
+ val label = pm.getApplicationLabel(appInfo)
+ val icon = pm.getApplicationIcon(appInfo)
+ return AppSnippet(label, icon)
+ }
+
+ /**
+ * Generates an [AppSnippet] containing an appIcon and appLabel from the
+ * supplied APK file
+ */
+ @JvmStatic
+ fun getAppSnippet(context: Context, pkgInfo: PackageInfo, sourceFile: File): AppSnippet {
+ pkgInfo.applicationInfo?.let {
+ val appInfoFromFile = processAppInfoForFile(it, sourceFile)
+ val label = getAppLabelFromFile(context, appInfoFromFile)
+ val icon = getAppIconFromFile(context, appInfoFromFile)
+ return AppSnippet(label, icon)
+ } ?: run {
+ return AppSnippet(pkgInfo.packageName, context.packageManager.defaultActivityIcon)
+ }
+ }
+
+ /**
+ * Utility method to load application label
+ *
+ * @param context context of package that can load the resources
+ * @param appInfo ApplicationInfo object of package whose resources are to be loaded
+ */
+ private fun getAppLabelFromFile(context: Context, appInfo: ApplicationInfo): CharSequence? {
+ val pm = context.packageManager
+ var label: CharSequence? = null
+ // Try to load the label from the package's resources. If an app has not explicitly
+ // specified any label, just use the package name.
+ if (appInfo.labelRes != 0) {
+ try {
+ label = appInfo.loadLabel(pm)
+ } catch (e: Resources.NotFoundException) {
+ }
+ }
+ if (label == null) {
+ label = if (appInfo.nonLocalizedLabel != null) appInfo.nonLocalizedLabel
+ else appInfo.packageName
+ }
+ return label
+ }
+
+ /**
+ * Utility method to load application icon
+ *
+ * @param context context of package that can load the resources
+ * @param appInfo ApplicationInfo object of package whose resources are to be loaded
+ */
+ private fun getAppIconFromFile(context: Context, appInfo: ApplicationInfo): Drawable? {
+ val pm = context.packageManager
+ var icon: Drawable? = null
+ // Try to load the icon from the package's resources. If an app has not explicitly
+ // specified any resource, just use the default icon for now.
+ try {
+ if (appInfo.icon != 0) {
+ try {
+ icon = appInfo.loadIcon(pm)
+ } catch (e: Resources.NotFoundException) {
+ }
+ }
+ if (icon == null) {
+ icon = context.packageManager.defaultActivityIcon
+ }
+ } catch (e: OutOfMemoryError) {
+ Log.i(LOG_TAG, "Could not load app icon", e)
+ }
+ return icon
+ }
+
+ private fun processAppInfoForFile(appInfo: ApplicationInfo, sourceFile: File): ApplicationInfo {
+ val archiveFilePath = sourceFile.absolutePath
+ appInfo.publicSourceDir = archiveFilePath
+ if (appInfo.splitNames != null && appInfo.splitSourceDirs == null) {
+ val files = sourceFile.parentFile?.listFiles()
+ val splits = appInfo.splitNames!!
+ .mapNotNull { findFilePath(files, "$it.apk") }
+ .toTypedArray()
+
+ appInfo.splitSourceDirs = splits
+ appInfo.splitPublicSourceDirs = splits
+ }
+ return appInfo
+ }
+
+ private fun findFilePath(files: Array<File>?, postfix: String): String? {
+ files?.let {
+ for (file in it) {
+ val path = file.absolutePath
+ if (path.endsWith(postfix)) {
+ return path
+ }
+ }
+ }
+ return null
+ }
+
+ /**
+ * @return the packageName corresponding to a UID.
+ */
+ @JvmStatic
+ fun getPackageNameForUid(context: Context, sourceUid: Int, callingPackage: String?): String? {
+ if (sourceUid == Process.INVALID_UID) {
+ return null
+ }
+ // If the sourceUid belongs to the system downloads provider, we explicitly return the
+ // name of the Download Manager package. This is because its UID is shared with multiple
+ // packages, resulting in uncertainty about which package will end up first in the list
+ // of packages associated with this UID
+ val pm = context.packageManager
+ val systemDownloadProviderInfo = getSystemDownloadsProviderInfo(pm, sourceUid)
+ if (systemDownloadProviderInfo != null) {
+ return systemDownloadProviderInfo.packageName
+ }
+ val packagesForUid = pm.getPackagesForUid(sourceUid) ?: return null
+ if (packagesForUid.size > 1) {
+ if (callingPackage != null) {
+ for (packageName in packagesForUid) {
+ if (packageName == callingPackage) {
+ return packageName
+ }
+ }
+ }
+ Log.i(LOG_TAG, "Multiple packages found for source uid $sourceUid")
+ }
+ return packagesForUid[0]
+ }
+
+ /**
+ * Utility method to get package information for a given [File]
+ */
+ @JvmStatic
+ fun getPackageInfo(context: Context, sourceFile: File, flags: Int): PackageInfo? {
+ var filePath = sourceFile.absolutePath
+ if (filePath.endsWith(SPLIT_BASE_APK_END_WITH)) {
+ val dir = sourceFile.parentFile
+ if ((dir?.listFiles()?.size ?: 0) > 1) {
+ // split apks, use file directory to get archive info
+ filePath = dir.path
+ }
+ }
+ return try {
+ context.packageManager.getPackageArchiveInfo(filePath, flags)
+ } catch (ignored: Exception) {
+ null
+ }
+ }
+
+ /**
+ * Is a profile part of a user?
+ *
+ * @param userManager The user manager
+ * @param userHandle The handle of the user
+ * @param profileHandle The handle of the profile
+ *
+ * @return If the profile is part of the user or the profile parent of the user
+ */
+ @JvmStatic
+ fun isProfileOfOrSame(
+ userManager: UserManager,
+ userHandle: UserHandle,
+ profileHandle: UserHandle?,
+ ): Boolean {
+ if (profileHandle == null) {
+ return false
+ }
+ return if (userHandle == profileHandle) {
+ true
+ } else userManager.getProfileParent(profileHandle) != null
+ && userManager.getProfileParent(profileHandle) == userHandle
+ }
+
+ /**
+ * The class to hold an incoming package's icon and label.
+ * See [getAppSnippet]
+ */
+ data class AppSnippet(var label: CharSequence?, var icon: Drawable?)
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/SessionStager.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/SessionStager.java
deleted file mode 100644
index a2c81f11cf68..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/SessionStager.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.model;
-
-import static android.content.res.AssetFileDescriptor.UNKNOWN_LENGTH;
-
-import android.content.Context;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageInstaller.SessionInfo;
-import android.content.res.AssetFileDescriptor;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.util.Log;
-import androidx.lifecycle.MutableLiveData;
-import com.android.packageinstaller.v2.model.InstallRepository.SessionStageListener;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-public class SessionStager extends AsyncTask<Void, Integer, SessionInfo> {
-
- private static final String TAG = SessionStager.class.getSimpleName();
- private final Context mContext;
- private final Uri mUri;
- private final int mStagedSessionId;
- private final MutableLiveData<Integer> mProgressLiveData = new MutableLiveData<>(0);
- private final SessionStageListener mListener;
-
- SessionStager(Context context, Uri uri, int stagedSessionId, SessionStageListener listener) {
- mContext = context;
- mUri = uri;
- mStagedSessionId = stagedSessionId;
- mListener = listener;
- }
-
- @Override
- protected PackageInstaller.SessionInfo doInBackground(Void... params) {
- PackageInstaller pi = mContext.getPackageManager().getPackageInstaller();
- try (PackageInstaller.Session session = pi.openSession(mStagedSessionId);
- InputStream in = mContext.getContentResolver().openInputStream(mUri)) {
- session.setStagingProgress(0);
-
- if (in == null) {
- return null;
- }
- final long sizeBytes = getContentSizeBytes();
- mProgressLiveData.postValue(sizeBytes > 0 ? 0 : -1);
-
- long totalRead = 0;
- try (OutputStream out = session.openWrite("PackageInstaller", 0, sizeBytes)) {
- byte[] buffer = new byte[1024 * 1024];
- while (true) {
- int numRead = in.read(buffer);
-
- if (numRead == -1) {
- session.fsync(out);
- break;
- }
-
- if (isCancelled()) {
- break;
- }
-
- out.write(buffer, 0, numRead);
- if (sizeBytes > 0) {
- totalRead += numRead;
- float fraction = ((float) totalRead / (float) sizeBytes);
- session.setStagingProgress(fraction);
- publishProgress((int) (fraction * 100.0));
- }
- }
- }
- return pi.getSessionInfo(mStagedSessionId);
- } catch (IOException | SecurityException | IllegalStateException
- | IllegalArgumentException e) {
- Log.w(TAG, "Error staging apk from content URI", e);
- return null;
- }
- }
-
- private long getContentSizeBytes() {
- try (AssetFileDescriptor afd = mContext.getContentResolver()
- .openAssetFileDescriptor(mUri, "r")) {
- return afd != null ? afd.getLength() : UNKNOWN_LENGTH;
- } catch (IOException e) {
- Log.w(TAG, "Failed to open asset file descriptor", e);
- return UNKNOWN_LENGTH;
- }
- }
-
- public MutableLiveData<Integer> getProgress() {
- return mProgressLiveData;
- }
-
- @Override
- protected void onProgressUpdate(Integer... progress) {
- if (progress != null && progress.length > 0) {
- mProgressLiveData.setValue(progress[0]);
- }
- }
-
- @Override
- protected void onPostExecute(SessionInfo sessionInfo) {
- if (sessionInfo == null || !sessionInfo.isActive()
- || sessionInfo.getResolvedBaseApkPath() == null) {
- Log.w(TAG, "Session info is invalid: " + sessionInfo);
- mListener.onStagingFailure();
- return;
- }
- mListener.onStagingSuccess(sessionInfo);
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/SessionStager.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/SessionStager.kt
new file mode 100644
index 000000000000..c9bfa17d80dc
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/SessionStager.kt
@@ -0,0 +1,110 @@
+/*
+ * 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.packageinstaller.v2.model
+
+import android.content.Context
+import android.content.pm.PackageInstaller
+import android.content.res.AssetFileDescriptor
+import android.net.Uri
+import android.util.Log
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import java.io.IOException
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+
+class SessionStager internal constructor(
+ private val context: Context,
+ private val uri: Uri,
+ private val stagedSessionId: Int
+) {
+
+ companion object {
+ private val LOG_TAG = SessionStager::class.java.simpleName
+ }
+
+ private val _progress = MutableLiveData(0)
+ val progress: LiveData<Int>
+ get() = _progress
+
+ suspend fun execute(): Boolean = withContext(Dispatchers.IO) {
+ val pi: PackageInstaller = context.packageManager.packageInstaller
+ var sessionInfo: PackageInstaller.SessionInfo?
+ try {
+ val session = pi.openSession(stagedSessionId)
+ context.contentResolver.openInputStream(uri).use { instream ->
+ session.setStagingProgress(0f)
+
+ if (instream == null) {
+ return@withContext false
+ }
+
+ val sizeBytes = getContentSizeBytes()
+ publishProgress(if (sizeBytes > 0) 0 else -1)
+
+ var totalRead: Long = 0
+ session.openWrite("PackageInstaller", 0, sizeBytes).use { out ->
+ val buffer = ByteArray(1024 * 1024)
+ while (true) {
+ val numRead = instream.read(buffer)
+ if (numRead == -1) {
+ session.fsync(out)
+ break
+ }
+ out.write(buffer, 0, numRead)
+
+ if (sizeBytes > 0) {
+ totalRead += numRead.toLong()
+ val fraction = totalRead.toFloat() / sizeBytes.toFloat()
+ session.setStagingProgress(fraction)
+ publishProgress((fraction * 100.0).toInt())
+ }
+ }
+ }
+ sessionInfo = pi.getSessionInfo(stagedSessionId)
+ }
+ } catch (e: Exception) {
+ Log.w(LOG_TAG, "Error staging apk from content URI", e)
+ sessionInfo = null
+ }
+
+ return@withContext if (sessionInfo == null
+ || !sessionInfo?.isActive!!
+ || sessionInfo?.resolvedBaseApkPath == null
+ ) {
+ Log.w(LOG_TAG, "Session info is invalid: $sessionInfo")
+ false
+ } else {
+ true
+ }
+ }
+
+ private fun getContentSizeBytes(): Long {
+ return try {
+ context.contentResolver
+ .openAssetFileDescriptor(uri, "r")
+ .use { afd -> afd?.length ?: AssetFileDescriptor.UNKNOWN_LENGTH }
+ } catch (e: IOException) {
+ Log.w(LOG_TAG, "Failed to open asset file descriptor", e)
+ AssetFileDescriptor.UNKNOWN_LENGTH
+ }
+ }
+
+ private suspend fun publishProgress(progressValue: Int) = withContext(Dispatchers.Main) {
+ _progress.value = progressValue
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.java
deleted file mode 100644
index a07c5326fa11..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.java
+++ /dev/null
@@ -1,716 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://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.packageinstaller.v2.model;
-
-import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
-import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
-import static com.android.packageinstaller.v2.model.PackageUtil.getMaxTargetSdkVersionForUid;
-import static com.android.packageinstaller.v2.model.PackageUtil.getPackageNameForUid;
-import static com.android.packageinstaller.v2.model.PackageUtil.isPermissionGranted;
-import static com.android.packageinstaller.v2.model.PackageUtil.isProfileOfOrSame;
-import static com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted.ABORT_REASON_APP_UNAVAILABLE;
-import static com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted.ABORT_REASON_GENERIC_ERROR;
-import static com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted.ABORT_REASON_USER_NOT_ALLOWED;
-
-import android.Manifest;
-import android.app.Activity;
-import android.app.AppOpsManager;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.admin.DevicePolicyManager;
-import android.app.usage.StorageStats;
-import android.app.usage.StorageStatsManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.UninstallCompleteCallback;
-import android.content.pm.VersionedPackage;
-import android.graphics.drawable.Icon;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Process;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.util.Log;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.lifecycle.MutableLiveData;
-import com.android.packageinstaller.R;
-import com.android.packageinstaller.common.EventResultPersister;
-import com.android.packageinstaller.common.UninstallEventReceiver;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallFailed;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallReady;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallStage;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallSuccess;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallUninstalling;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallUserActionRequired;
-import java.io.IOException;
-import java.util.List;
-
-public class UninstallRepository {
-
- private static final String TAG = UninstallRepository.class.getSimpleName();
- private static final String UNINSTALL_FAILURE_CHANNEL = "uninstall_failure";
- private static final String BROADCAST_ACTION =
- "com.android.packageinstaller.ACTION_UNINSTALL_COMMIT";
-
- private static final String EXTRA_UNINSTALL_ID =
- "com.android.packageinstaller.extra.UNINSTALL_ID";
- private static final String EXTRA_APP_LABEL =
- "com.android.packageinstaller.extra.APP_LABEL";
- private static final String EXTRA_IS_CLONE_APP =
- "com.android.packageinstaller.extra.IS_CLONE_APP";
- private static final String EXTRA_PACKAGE_NAME =
- "com.android.packageinstaller.extra.EXTRA_PACKAGE_NAME";
-
- private final Context mContext;
- private final AppOpsManager mAppOpsManager;
- private final PackageManager mPackageManager;
- private final UserManager mUserManager;
- private final NotificationManager mNotificationManager;
- private final MutableLiveData<UninstallStage> mUninstallResult = new MutableLiveData<>();
- public UserHandle mUninstalledUser;
- public UninstallCompleteCallback mCallback;
- private ApplicationInfo mTargetAppInfo;
- private ActivityInfo mTargetActivityInfo;
- private Intent mIntent;
- private CharSequence mTargetAppLabel;
- private String mTargetPackageName;
- private String mCallingActivity;
- private boolean mUninstallFromAllUsers;
- private boolean mIsClonedApp;
- private int mUninstallId;
-
- public UninstallRepository(Context context) {
- mContext = context;
- mAppOpsManager = context.getSystemService(AppOpsManager.class);
- mPackageManager = context.getPackageManager();
- mUserManager = context.getSystemService(UserManager.class);
- mNotificationManager = context.getSystemService(NotificationManager.class);
- }
-
- public UninstallStage performPreUninstallChecks(Intent intent, CallerInfo callerInfo) {
- mIntent = intent;
-
- int callingUid = callerInfo.getUid();
- mCallingActivity = callerInfo.getActivityName();
-
- if (callingUid == Process.INVALID_UID) {
- Log.e(TAG, "Could not determine the launching uid.");
- return new UninstallAborted(ABORT_REASON_GENERIC_ERROR);
- // TODO: should we give any indication to the user?
- }
-
- String callingPackage = getPackageNameForUid(mContext, callingUid, null);
- if (callingPackage == null) {
- Log.e(TAG, "Package not found for originating uid " + callingUid);
- return new UninstallAborted(ABORT_REASON_GENERIC_ERROR);
- } else {
- if (mAppOpsManager.noteOpNoThrow(
- AppOpsManager.OPSTR_REQUEST_DELETE_PACKAGES, callingUid, callingPackage)
- != MODE_ALLOWED) {
- Log.e(TAG, "Install from uid " + callingUid + " disallowed by AppOps");
- return new UninstallAborted(ABORT_REASON_GENERIC_ERROR);
- }
- }
-
- if (getMaxTargetSdkVersionForUid(mContext, callingUid) >= Build.VERSION_CODES.P
- && !isPermissionGranted(mContext, Manifest.permission.REQUEST_DELETE_PACKAGES,
- callingUid)
- && !isPermissionGranted(mContext, Manifest.permission.DELETE_PACKAGES, callingUid)) {
- Log.e(TAG, "Uid " + callingUid + " does not have "
- + Manifest.permission.REQUEST_DELETE_PACKAGES + " or "
- + Manifest.permission.DELETE_PACKAGES);
-
- return new UninstallAborted(ABORT_REASON_GENERIC_ERROR);
- }
-
- // Get intent information.
- // We expect an intent with URI of the form package:<packageName>#<className>
- // className is optional; if specified, it is the activity the user chose to uninstall
- final Uri packageUri = intent.getData();
- if (packageUri == null) {
- Log.e(TAG, "No package URI in intent");
- return new UninstallAborted(ABORT_REASON_APP_UNAVAILABLE);
- }
- mTargetPackageName = packageUri.getEncodedSchemeSpecificPart();
- if (mTargetPackageName == null) {
- Log.e(TAG, "Invalid package name in URI: " + packageUri);
- return new UninstallAborted(ABORT_REASON_APP_UNAVAILABLE);
- }
-
- mUninstallFromAllUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS,
- false);
- if (mUninstallFromAllUsers && !mUserManager.isAdminUser()) {
- Log.e(TAG, "Only admin user can request uninstall for all users");
- return new UninstallAborted(ABORT_REASON_USER_NOT_ALLOWED);
- }
-
- mUninstalledUser = intent.getParcelableExtra(Intent.EXTRA_USER, UserHandle.class);
- if (mUninstalledUser == null) {
- mUninstalledUser = Process.myUserHandle();
- } else {
- List<UserHandle> profiles = mUserManager.getUserProfiles();
- if (!profiles.contains(mUninstalledUser)) {
- Log.e(TAG, "User " + Process.myUserHandle() + " can't request uninstall "
- + "for user " + mUninstalledUser);
- return new UninstallAborted(ABORT_REASON_USER_NOT_ALLOWED);
- }
- }
-
- mCallback = intent.getParcelableExtra(PackageInstaller.EXTRA_CALLBACK,
- PackageManager.UninstallCompleteCallback.class);
-
- try {
- mTargetAppInfo = mPackageManager.getApplicationInfo(mTargetPackageName,
- PackageManager.ApplicationInfoFlags.of(PackageManager.MATCH_ANY_USER));
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "Unable to get packageName");
- }
-
- if (mTargetAppInfo == null) {
- Log.e(TAG, "Invalid packageName: " + mTargetPackageName);
- return new UninstallAborted(ABORT_REASON_APP_UNAVAILABLE);
- }
-
- // The class name may have been specified (e.g. when deleting an app from all apps)
- final String className = packageUri.getFragment();
- if (className != null) {
- try {
- mTargetActivityInfo = mPackageManager.getActivityInfo(
- new ComponentName(mTargetPackageName, className),
- PackageManager.ComponentInfoFlags.of(0));
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "Unable to get className");
- // Continue as the ActivityInfo isn't critical.
- }
- }
-
- return new UninstallReady();
- }
-
- public UninstallStage generateUninstallDetails() {
- UninstallUserActionRequired.Builder uarBuilder = new UninstallUserActionRequired.Builder();
- StringBuilder messageBuilder = new StringBuilder();
-
- mTargetAppLabel = mTargetAppInfo.loadSafeLabel(mPackageManager);
-
- // If the Activity label differs from the App label, then make sure the user
- // knows the Activity belongs to the App being uninstalled.
- if (mTargetActivityInfo != null) {
- final CharSequence activityLabel = mTargetActivityInfo.loadSafeLabel(mPackageManager);
- if (CharSequence.compare(activityLabel, mTargetAppLabel) != 0) {
- messageBuilder.append(
- mContext.getString(R.string.uninstall_activity_text, activityLabel));
- messageBuilder.append(" ").append(mTargetAppLabel).append(".\n\n");
- }
- }
-
- final boolean isUpdate =
- (mTargetAppInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
- final UserHandle myUserHandle = Process.myUserHandle();
- boolean isSingleUser = isSingleUser();
-
- if (isUpdate) {
- messageBuilder.append(mContext.getString(
- isSingleUser ? R.string.uninstall_update_text :
- R.string.uninstall_update_text_multiuser));
- } else if (mUninstallFromAllUsers && !isSingleUser) {
- messageBuilder.append(mContext.getString(
- R.string.uninstall_application_text_all_users));
- } else if (!mUninstalledUser.equals(myUserHandle)) {
- // Uninstalling user is issuing uninstall for another user
- UserManager customUserManager = mContext.createContextAsUser(mUninstalledUser, 0)
- .getSystemService(UserManager.class);
- String userName = customUserManager.getUserName();
-
- String uninstalledUserType = getUninstalledUserType(myUserHandle, mUninstalledUser);
- String messageString;
- if (USER_TYPE_PROFILE_MANAGED.equals(uninstalledUserType)) {
- messageString = mContext.getString(
- R.string.uninstall_application_text_current_user_work_profile, userName);
- } else if (USER_TYPE_PROFILE_CLONE.equals(uninstalledUserType)) {
- mIsClonedApp = true;
- messageString = mContext.getString(
- R.string.uninstall_application_text_current_user_clone_profile);
- } else {
- messageString = mContext.getString(
- R.string.uninstall_application_text_user, userName);
- }
- messageBuilder.append(messageString);
- } else if (isCloneProfile(mUninstalledUser)) {
- mIsClonedApp = true;
- messageBuilder.append(mContext.getString(
- R.string.uninstall_application_text_current_user_clone_profile));
- } else if (myUserHandle.equals(UserHandle.SYSTEM)
- && hasClonedInstance(mTargetAppInfo.packageName)) {
- messageBuilder.append(mContext.getString(
- R.string.uninstall_application_text_with_clone_instance, mTargetAppLabel));
- } else {
- messageBuilder.append(mContext.getString(R.string.uninstall_application_text));
- }
-
- uarBuilder.setMessage(messageBuilder.toString());
-
- if (mIsClonedApp) {
- uarBuilder.setTitle(mContext.getString(R.string.cloned_app_label, mTargetAppLabel));
- } else {
- uarBuilder.setTitle(mTargetAppLabel.toString());
- }
-
- boolean suggestToKeepAppData = false;
- try {
- PackageInfo pkgInfo = mPackageManager.getPackageInfo(mTargetPackageName, 0);
- suggestToKeepAppData =
- pkgInfo.applicationInfo != null && pkgInfo.applicationInfo.hasFragileUserData();
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "Cannot check hasFragileUserData for " + mTargetPackageName, e);
- }
-
- long appDataSize = 0;
- if (suggestToKeepAppData) {
- appDataSize = getAppDataSize(mTargetPackageName,
- mUninstallFromAllUsers ? null : mUninstalledUser);
- }
- uarBuilder.setAppDataSize(appDataSize);
-
- return uarBuilder.build();
- }
-
- /**
- * Returns whether there is only one "full" user on this device.
- *
- * <p><b>Note:</b> on devices that use {@link android.os.UserManager#isHeadlessSystemUserMode()
- * headless system user mode}, the system user is not "full", so it's not be considered in the
- * calculation.</p>
- */
- private boolean isSingleUser() {
- final int userCount = mUserManager.getUserCount();
- return userCount == 1 || (UserManager.isHeadlessSystemUserMode() && userCount == 2);
- }
-
- /**
- * Returns the type of the user from where an app is being uninstalled. We are concerned with
- * only USER_TYPE_PROFILE_MANAGED and USER_TYPE_PROFILE_CLONE and whether the user and profile
- * belong to the same profile group.
- */
- @Nullable
- private String getUninstalledUserType(UserHandle myUserHandle,
- UserHandle uninstalledUserHandle) {
- if (!mUserManager.isSameProfileGroup(myUserHandle, uninstalledUserHandle)) {
- return null;
- }
-
- UserManager customUserManager = mContext.createContextAsUser(uninstalledUserHandle, 0)
- .getSystemService(UserManager.class);
- String[] userTypes = {USER_TYPE_PROFILE_MANAGED, USER_TYPE_PROFILE_CLONE};
- for (String userType : userTypes) {
- if (customUserManager.isUserOfType(userType)) {
- return userType;
- }
- }
- return null;
- }
-
- private boolean hasClonedInstance(String packageName) {
- // Check if clone user is present on the device.
- UserHandle cloneUser = null;
- List<UserHandle> profiles = mUserManager.getUserProfiles();
- for (UserHandle userHandle : profiles) {
- if (!userHandle.equals(UserHandle.SYSTEM) && isCloneProfile(userHandle)) {
- cloneUser = userHandle;
- break;
- }
- }
- // Check if another instance of given package exists in clone user profile.
- try {
- return cloneUser != null
- && mPackageManager.getPackageUidAsUser(packageName,
- PackageManager.PackageInfoFlags.of(0), cloneUser.getIdentifier()) > 0;
- } catch (PackageManager.NameNotFoundException e) {
- return false;
- }
- }
-
- private boolean isCloneProfile(UserHandle userHandle) {
- UserManager customUserManager = mContext.createContextAsUser(userHandle, 0)
- .getSystemService(UserManager.class);
- return customUserManager.isUserOfType(UserManager.USER_TYPE_PROFILE_CLONE);
- }
-
- /**
- * Get number of bytes of the app data of the package.
- *
- * @param pkg The package that might have app data.
- * @param user The user the package belongs to or {@code null} if files of all users should
- * be counted.
- * @return The number of bytes.
- */
- private long getAppDataSize(@NonNull String pkg, @Nullable UserHandle user) {
- if (user != null) {
- return getAppDataSizeForUser(pkg, user);
- }
- // We are uninstalling from all users. Get cumulative app data size for all users.
- List<UserHandle> userHandles = mUserManager.getUserHandles(true);
- long totalAppDataSize = 0;
- int numUsers = userHandles.size();
- for (int i = 0; i < numUsers; i++) {
- totalAppDataSize += getAppDataSizeForUser(pkg, userHandles.get(i));
- }
- return totalAppDataSize;
- }
-
- /**
- * Get number of bytes of the app data of the package.
- *
- * @param pkg The package that might have app data.
- * @param user The user the package belongs to
- * @return The number of bytes.
- */
- private long getAppDataSizeForUser(@NonNull String pkg, @NonNull UserHandle user) {
- StorageStatsManager storageStatsManager =
- mContext.getSystemService(StorageStatsManager.class);
- try {
- StorageStats stats = storageStatsManager.queryStatsForPackage(
- mPackageManager.getApplicationInfo(pkg, 0).storageUuid, pkg, user);
- return stats.getDataBytes();
- } catch (PackageManager.NameNotFoundException | IOException | SecurityException e) {
- Log.e(TAG, "Cannot determine amount of app data for " + pkg, e);
- }
- return 0;
- }
-
- public void initiateUninstall(boolean keepData) {
- // Get an uninstallId to track results and show a notification on non-TV devices.
- try {
- mUninstallId = UninstallEventReceiver.addObserver(mContext,
- EventResultPersister.GENERATE_NEW_ID, this::handleUninstallResult);
- } catch (EventResultPersister.OutOfIdsException e) {
- Log.e(TAG, "Failed to start uninstall", e);
- handleUninstallResult(PackageInstaller.STATUS_FAILURE,
- PackageManager.DELETE_FAILED_INTERNAL_ERROR, null, 0);
- return;
- }
-
- // TODO: Check with UX whether to show UninstallUninstalling dialog / notification?
- mUninstallResult.setValue(new UninstallUninstalling(mTargetAppLabel, mIsClonedApp));
-
- Bundle uninstallData = new Bundle();
- uninstallData.putInt(EXTRA_UNINSTALL_ID, mUninstallId);
- uninstallData.putString(EXTRA_PACKAGE_NAME, mTargetPackageName);
- uninstallData.putBoolean(Intent.EXTRA_UNINSTALL_ALL_USERS, mUninstallFromAllUsers);
- uninstallData.putCharSequence(EXTRA_APP_LABEL, mTargetAppLabel);
- uninstallData.putBoolean(EXTRA_IS_CLONE_APP, mIsClonedApp);
- Log.i(TAG, "Uninstalling extras = " + uninstallData);
-
- // Get a PendingIntent for result broadcast and issue an uninstall request
- Intent broadcastIntent = new Intent(BROADCAST_ACTION);
- broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mUninstallId);
- broadcastIntent.setPackage(mContext.getPackageName());
-
- PendingIntent pendingIntent =
- PendingIntent.getBroadcast(mContext, mUninstallId, broadcastIntent,
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
-
- if (!startUninstall(mTargetPackageName, mUninstalledUser, pendingIntent,
- mUninstallFromAllUsers, keepData)) {
- handleUninstallResult(PackageInstaller.STATUS_FAILURE,
- PackageManager.DELETE_FAILED_INTERNAL_ERROR, null, 0);
- }
- }
-
- private void handleUninstallResult(int status, int legacyStatus, @Nullable String message,
- int serviceId) {
- if (mCallback != null) {
- // The caller will be informed about the result via a callback
- mCallback.onUninstallComplete(mTargetPackageName, legacyStatus, message);
-
- // Since the caller already received the results, just finish the app at this point
- mUninstallResult.setValue(null);
- return;
- }
-
- boolean returnResult = mIntent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false);
- if (returnResult || mCallingActivity != null) {
- Intent intent = new Intent();
- intent.putExtra(Intent.EXTRA_INSTALL_RESULT, legacyStatus);
-
- if (status == PackageInstaller.STATUS_SUCCESS) {
- UninstallSuccess.Builder successBuilder = new UninstallSuccess.Builder()
- .setResultIntent(intent)
- .setActivityResultCode(Activity.RESULT_OK);
- mUninstallResult.setValue(successBuilder.build());
- } else {
- UninstallFailed.Builder failedBuilder = new UninstallFailed.Builder(true)
- .setResultIntent(intent)
- .setActivityResultCode(Activity.RESULT_FIRST_USER);
- mUninstallResult.setValue(failedBuilder.build());
- }
- return;
- }
-
- // Caller did not want the result back. So, we either show a Toast, or a Notification.
- if (status == PackageInstaller.STATUS_SUCCESS) {
- UninstallSuccess.Builder successBuilder = new UninstallSuccess.Builder()
- .setActivityResultCode(legacyStatus)
- .setMessage(mIsClonedApp
- ? mContext.getString(R.string.uninstall_done_clone_app, mTargetAppLabel)
- : mContext.getString(R.string.uninstall_done_app, mTargetAppLabel));
- mUninstallResult.setValue(successBuilder.build());
- } else {
- UninstallFailed.Builder failedBuilder = new UninstallFailed.Builder(false);
- Notification.Builder uninstallFailedNotification = null;
-
- NotificationChannel uninstallFailureChannel = new NotificationChannel(
- UNINSTALL_FAILURE_CHANNEL,
- mContext.getString(R.string.uninstall_failure_notification_channel),
- NotificationManager.IMPORTANCE_DEFAULT);
- mNotificationManager.createNotificationChannel(uninstallFailureChannel);
-
- uninstallFailedNotification = new Notification.Builder(mContext,
- UNINSTALL_FAILURE_CHANNEL);
-
- UserHandle myUserHandle = Process.myUserHandle();
- switch (legacyStatus) {
- case PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER -> {
- // Find out if the package is an active admin for some non-current user.
- UserHandle otherBlockingUserHandle =
- findUserOfDeviceAdmin(myUserHandle, mTargetPackageName);
-
- if (otherBlockingUserHandle == null) {
- Log.d(TAG, "Uninstall failed because " + mTargetPackageName
- + " is a device admin");
-
- addDeviceManagerButton(mContext, uninstallFailedNotification);
- setBigText(uninstallFailedNotification, mContext.getString(
- R.string.uninstall_failed_device_policy_manager));
- } else {
- Log.d(TAG, "Uninstall failed because " + mTargetPackageName
- + " is a device admin of user " + otherBlockingUserHandle);
-
- String userName =
- mContext.createContextAsUser(otherBlockingUserHandle, 0)
- .getSystemService(UserManager.class).getUserName();
- setBigText(uninstallFailedNotification, String.format(
- mContext.getString(
- R.string.uninstall_failed_device_policy_manager_of_user),
- userName));
- }
- }
- case PackageManager.DELETE_FAILED_OWNER_BLOCKED -> {
- UserHandle otherBlockingUserHandle = findBlockingUser(mTargetPackageName);
- boolean isProfileOfOrSame = isProfileOfOrSame(mUserManager, myUserHandle,
- otherBlockingUserHandle);
-
- if (isProfileOfOrSame) {
- addDeviceManagerButton(mContext, uninstallFailedNotification);
- } else {
- addManageUsersButton(mContext, uninstallFailedNotification);
- }
-
- String bigText = null;
- if (otherBlockingUserHandle == null) {
- Log.d(TAG, "Uninstall failed for " + mTargetPackageName +
- " with code " + status + " no blocking user");
- } else if (otherBlockingUserHandle == UserHandle.SYSTEM) {
- bigText = mContext.getString(
- R.string.uninstall_blocked_device_owner);
- } else {
- bigText = mContext.getString(mUninstallFromAllUsers ?
- R.string.uninstall_all_blocked_profile_owner
- : R.string.uninstall_blocked_profile_owner);
- }
- if (bigText != null) {
- setBigText(uninstallFailedNotification, bigText);
- }
- }
- default -> {
- Log.d(TAG, "Uninstall blocked for " + mTargetPackageName
- + " with legacy code " + legacyStatus);
- }
- }
-
- uninstallFailedNotification.setContentTitle(
- mContext.getString(R.string.uninstall_failed_app, mTargetAppLabel));
- uninstallFailedNotification.setOngoing(false);
- uninstallFailedNotification.setSmallIcon(R.drawable.ic_error);
- failedBuilder.setUninstallNotification(mUninstallId,
- uninstallFailedNotification.build());
-
- mUninstallResult.setValue(failedBuilder.build());
- }
- }
-
- /**
- * @param myUserHandle {@link UserHandle} of the current user.
- * @param packageName Name of the package being uninstalled.
- * @return the {@link UserHandle} of the user in which a package is a device admin.
- */
- @Nullable
- private UserHandle findUserOfDeviceAdmin(UserHandle myUserHandle, String packageName) {
- for (UserHandle otherUserHandle : mUserManager.getUserHandles(true)) {
- // We only catch the case when the user in question is neither the
- // current user nor its profile.
- if (isProfileOfOrSame(mUserManager, myUserHandle, otherUserHandle)) {
- continue;
- }
- DevicePolicyManager dpm = mContext.createContextAsUser(otherUserHandle, 0)
- .getSystemService(DevicePolicyManager.class);
- if (dpm.packageHasActiveAdmins(packageName)) {
- return otherUserHandle;
- }
- }
- return null;
- }
-
- /**
- *
- * @param packageName Name of the package being uninstalled.
- * @return {@link UserHandle} of the user in which a package is blocked from being uninstalled.
- */
- @Nullable
- private UserHandle findBlockingUser(String packageName) {
- for (UserHandle otherUserHandle : mUserManager.getUserHandles(true)) {
- // TODO (b/307399586): Add a negation when the logic of the method
- // is fixed
- if (mPackageManager.canUserUninstall(packageName, otherUserHandle)) {
- return otherUserHandle;
- }
- }
- return null;
- }
-
- /**
- * Set big text for the notification.
- *
- * @param builder The builder of the notification
- * @param text The text to set.
- */
- private void setBigText(@NonNull Notification.Builder builder,
- @NonNull CharSequence text) {
- builder.setStyle(new Notification.BigTextStyle().bigText(text));
- }
-
- /**
- * Add a button to the notification that links to the user management.
- *
- * @param context The context the notification is created in
- * @param builder The builder of the notification
- */
- private void addManageUsersButton(@NonNull Context context,
- @NonNull Notification.Builder builder) {
- builder.addAction((new Notification.Action.Builder(
- Icon.createWithResource(context, R.drawable.ic_settings_multiuser),
- context.getString(R.string.manage_users),
- PendingIntent.getActivity(context, 0, getUserSettingsIntent(),
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))).build());
- }
-
- private Intent getUserSettingsIntent() {
- Intent intent = new Intent(Settings.ACTION_USER_SETTINGS);
- intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_NEW_TASK);
- return intent;
- }
-
- /**
- * Add a button to the notification that links to the device policy management.
- *
- * @param context The context the notification is created in
- * @param builder The builder of the notification
- */
- private void addDeviceManagerButton(@NonNull Context context,
- @NonNull Notification.Builder builder) {
- builder.addAction((new Notification.Action.Builder(
- Icon.createWithResource(context, R.drawable.ic_lock),
- context.getString(R.string.manage_device_administrators),
- PendingIntent.getActivity(context, 0, getDeviceManagerIntent(),
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))).build());
- }
-
- private Intent getDeviceManagerIntent() {
- Intent intent = new Intent();
- intent.setClassName("com.android.settings",
- "com.android.settings.Settings$DeviceAdminSettingsActivity");
- intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_NEW_TASK);
- return intent;
- }
-
- /**
- * Starts an uninstall for the given package.
- *
- * @return {@code true} if there was no exception while uninstalling. This does not represent
- * the result of the uninstall. Result will be made available in
- * {@link #handleUninstallResult(int, int, String, int)}
- */
- private boolean startUninstall(String packageName, UserHandle targetUser,
- PendingIntent pendingIntent, boolean uninstallFromAllUsers, boolean keepData) {
- int flags = uninstallFromAllUsers ? PackageManager.DELETE_ALL_USERS : 0;
- flags |= keepData ? PackageManager.DELETE_KEEP_DATA : 0;
- try {
- mContext.createContextAsUser(targetUser, 0)
- .getPackageManager().getPackageInstaller().uninstall(
- new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST),
- flags, pendingIntent.getIntentSender());
- return true;
- } catch (IllegalArgumentException e) {
- Log.e(TAG, "Failed to uninstall", e);
- return false;
- }
- }
-
- public void cancelInstall() {
- if (mCallback != null) {
- mCallback.onUninstallComplete(mTargetPackageName,
- PackageManager.DELETE_FAILED_ABORTED, "Cancelled by user");
- }
- }
-
- public MutableLiveData<UninstallStage> getUninstallResult() {
- return mUninstallResult;
- }
-
- public static class CallerInfo {
-
- private final String mActivityName;
- private final int mUid;
-
- public CallerInfo(String activityName, int uid) {
- mActivityName = activityName;
- mUid = uid;
- }
-
- public String getActivityName() {
- return mActivityName;
- }
-
- public int getUid() {
- return mUid;
- }
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt
new file mode 100644
index 000000000000..7cc95c5d7299
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt
@@ -0,0 +1,739 @@
+/*
+ * 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
+ *
+ * https://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.packageinstaller.v2.model
+
+import android.Manifest
+import android.app.Activity
+import android.app.AppOpsManager
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.app.admin.DevicePolicyManager
+import android.app.usage.StorageStatsManager
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInstaller
+import android.content.pm.PackageManager
+import android.content.pm.VersionedPackage
+import android.graphics.drawable.Icon
+import android.os.Build
+import android.os.Bundle
+import android.os.Process
+import android.os.UserHandle
+import android.os.UserManager
+import android.provider.Settings
+import android.util.Log
+import androidx.lifecycle.MutableLiveData
+import com.android.packageinstaller.R
+import com.android.packageinstaller.common.EventResultPersister
+import com.android.packageinstaller.common.EventResultPersister.OutOfIdsException
+import com.android.packageinstaller.common.UninstallEventReceiver
+import com.android.packageinstaller.v2.model.PackageUtil.getMaxTargetSdkVersionForUid
+import com.android.packageinstaller.v2.model.PackageUtil.getPackageNameForUid
+import com.android.packageinstaller.v2.model.PackageUtil.isPermissionGranted
+import com.android.packageinstaller.v2.model.PackageUtil.isProfileOfOrSame
+
+class UninstallRepository(private val context: Context) {
+
+ private val appOpsManager: AppOpsManager? = context.getSystemService(AppOpsManager::class.java)
+ private val packageManager: PackageManager = context.packageManager
+ private val userManager: UserManager? = context.getSystemService(UserManager::class.java)
+ private val notificationManager: NotificationManager? =
+ context.getSystemService(NotificationManager::class.java)
+ val uninstallResult = MutableLiveData<UninstallStage?>()
+ private var uninstalledUser: UserHandle? = null
+ private var callback: PackageManager.UninstallCompleteCallback? = null
+ private var targetAppInfo: ApplicationInfo? = null
+ private var targetActivityInfo: ActivityInfo? = null
+ private lateinit var intent: Intent
+ private lateinit var targetAppLabel: CharSequence
+ private var targetPackageName: String? = null
+ private var callingActivity: String? = null
+ private var uninstallFromAllUsers = false
+ private var isClonedApp = false
+ private var uninstallId = 0
+
+ fun performPreUninstallChecks(intent: Intent, callerInfo: CallerInfo): UninstallStage {
+ this.intent = intent
+
+ val callingUid = callerInfo.uid
+ callingActivity = callerInfo.activityName
+
+ if (callingUid == Process.INVALID_UID) {
+ Log.e(LOG_TAG, "Could not determine the launching uid.")
+ return UninstallAborted(UninstallAborted.ABORT_REASON_GENERIC_ERROR)
+ // TODO: should we give any indication to the user?
+ }
+
+ val callingPackage = getPackageNameForUid(context, callingUid, null)
+ if (callingPackage == null) {
+ Log.e(LOG_TAG, "Package not found for originating uid $callingUid")
+ return UninstallAborted(UninstallAborted.ABORT_REASON_GENERIC_ERROR)
+ } else {
+ if (appOpsManager!!.noteOpNoThrow(
+ AppOpsManager.OPSTR_REQUEST_DELETE_PACKAGES, callingUid, callingPackage
+ ) != AppOpsManager.MODE_ALLOWED
+ ) {
+ Log.e(LOG_TAG, "Install from uid $callingUid disallowed by AppOps")
+ return UninstallAborted(UninstallAborted.ABORT_REASON_GENERIC_ERROR)
+ }
+ }
+
+ if (getMaxTargetSdkVersionForUid(context, callingUid) >= Build.VERSION_CODES.P
+ && !isPermissionGranted(
+ context, Manifest.permission.REQUEST_DELETE_PACKAGES, callingUid
+ )
+ && !isPermissionGranted(context, Manifest.permission.DELETE_PACKAGES, callingUid)
+ ) {
+ Log.e(
+ LOG_TAG, "Uid " + callingUid + " does not have "
+ + Manifest.permission.REQUEST_DELETE_PACKAGES + " or "
+ + Manifest.permission.DELETE_PACKAGES
+ )
+ return UninstallAborted(UninstallAborted.ABORT_REASON_GENERIC_ERROR)
+ }
+
+ // Get intent information.
+ // We expect an intent with URI of the form package:<packageName>#<className>
+ // className is optional; if specified, it is the activity the user chose to uninstall
+ val packageUri = intent.data
+ if (packageUri == null) {
+ Log.e(LOG_TAG, "No package URI in intent")
+ return UninstallAborted(UninstallAborted.ABORT_REASON_APP_UNAVAILABLE)
+ }
+ targetPackageName = packageUri.encodedSchemeSpecificPart
+ if (targetPackageName == null) {
+ Log.e(LOG_TAG, "Invalid package name in URI: $packageUri")
+ return UninstallAborted(UninstallAborted.ABORT_REASON_APP_UNAVAILABLE)
+ }
+
+ uninstallFromAllUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false)
+ if (uninstallFromAllUsers && !userManager!!.isAdminUser) {
+ Log.e(LOG_TAG, "Only admin user can request uninstall for all users")
+ return UninstallAborted(UninstallAborted.ABORT_REASON_USER_NOT_ALLOWED)
+ }
+
+ uninstalledUser = intent.getParcelableExtra(Intent.EXTRA_USER, UserHandle::class.java)
+ if (uninstalledUser == null) {
+ uninstalledUser = Process.myUserHandle()
+ } else {
+ val profiles = userManager!!.userProfiles
+ if (!profiles.contains(uninstalledUser)) {
+ Log.e(
+ LOG_TAG, "User " + Process.myUserHandle() + " can't request uninstall "
+ + "for user " + uninstalledUser
+ )
+ return UninstallAborted(UninstallAborted.ABORT_REASON_USER_NOT_ALLOWED)
+ }
+ }
+
+ callback = intent.getParcelableExtra(
+ PackageInstaller.EXTRA_CALLBACK, PackageManager.UninstallCompleteCallback::class.java
+ )
+
+ try {
+ targetAppInfo = packageManager.getApplicationInfo(
+ targetPackageName!!,
+ PackageManager.ApplicationInfoFlags.of(PackageManager.MATCH_ANY_USER.toLong())
+ )
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.e(LOG_TAG, "Unable to get packageName")
+ }
+
+ if (targetAppInfo == null) {
+ Log.e(LOG_TAG, "Invalid packageName: $targetPackageName")
+ return UninstallAborted(UninstallAborted.ABORT_REASON_APP_UNAVAILABLE)
+ }
+
+ // The class name may have been specified (e.g. when deleting an app from all apps)
+ val className = packageUri.fragment
+ if (className != null) {
+ try {
+ targetActivityInfo = packageManager.getActivityInfo(
+ ComponentName(targetPackageName!!, className),
+ PackageManager.ComponentInfoFlags.of(0)
+ )
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.e(LOG_TAG, "Unable to get className")
+ // Continue as the ActivityInfo isn't critical.
+ }
+ }
+
+ return UninstallReady()
+ }
+
+ fun generateUninstallDetails(): UninstallStage {
+ val messageBuilder = StringBuilder()
+
+ targetAppLabel = targetAppInfo!!.loadSafeLabel(packageManager)
+
+ // If the Activity label differs from the App label, then make sure the user
+ // knows the Activity belongs to the App being uninstalled.
+ if (targetActivityInfo != null) {
+ val activityLabel = targetActivityInfo!!.loadSafeLabel(packageManager)
+ if (!activityLabel.contentEquals(targetAppLabel)) {
+ messageBuilder.append(
+ context.getString(R.string.uninstall_activity_text, activityLabel)
+ )
+ messageBuilder.append(" ").append(targetAppLabel).append(".\n\n")
+ }
+ }
+
+ val isUpdate = (targetAppInfo!!.flags and ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0
+ val myUserHandle = Process.myUserHandle()
+ val isSingleUser = isSingleUser()
+
+ if (isUpdate) {
+ messageBuilder.append(context.getString(
+ if (isSingleUser) R.string.uninstall_update_text
+ else R.string.uninstall_update_text_multiuser
+ )
+ )
+ } else if (uninstallFromAllUsers && !isSingleUser) {
+ messageBuilder.append(context.getString(R.string.uninstall_application_text_all_users))
+ } else if (uninstalledUser != myUserHandle) {
+ // Uninstalling user is issuing uninstall for another user
+ val customUserManager = context.createContextAsUser(uninstalledUser!!, 0)
+ .getSystemService(UserManager::class.java)
+ val userName = customUserManager!!.userName
+
+ val uninstalledUserType = getUninstalledUserType(myUserHandle, uninstalledUser!!)
+ val messageString: String
+ when (uninstalledUserType) {
+ UserManager.USER_TYPE_PROFILE_MANAGED -> {
+ messageString = context.getString(
+ R.string.uninstall_application_text_current_user_work_profile, userName
+ )
+ }
+
+ UserManager.USER_TYPE_PROFILE_CLONE -> {
+ isClonedApp = true
+ messageString = context.getString(
+ R.string.uninstall_application_text_current_user_clone_profile
+ )
+ }
+
+ else -> {
+ messageString = context.getString(
+ R.string.uninstall_application_text_user, userName
+ )
+ }
+
+ }
+ messageBuilder.append(messageString)
+ } else if (isCloneProfile(uninstalledUser!!)) {
+ isClonedApp = true
+ messageBuilder.append(context.getString(
+ R.string.uninstall_application_text_current_user_clone_profile
+ )
+ )
+ } else if (myUserHandle == UserHandle.SYSTEM
+ && hasClonedInstance(targetAppInfo!!.packageName)
+ ) {
+ messageBuilder.append(context.getString(
+ R.string.uninstall_application_text_with_clone_instance, targetAppLabel
+ )
+ )
+ } else {
+ messageBuilder.append(context.getString(R.string.uninstall_application_text))
+ }
+
+ val message = messageBuilder.toString()
+
+ val title = if (isClonedApp) {
+ context.getString(R.string.cloned_app_label, targetAppLabel)
+ } else {
+ targetAppLabel.toString()
+ }
+
+ var suggestToKeepAppData = false
+ try {
+ val pkgInfo = packageManager.getPackageInfo(targetPackageName!!, 0)
+ suggestToKeepAppData =
+ pkgInfo.applicationInfo != null && pkgInfo.applicationInfo!!.hasFragileUserData()
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.e(LOG_TAG, "Cannot check hasFragileUserData for $targetPackageName", e)
+ }
+
+ var appDataSize: Long = 0
+ if (suggestToKeepAppData) {
+ appDataSize = getAppDataSize(
+ targetPackageName!!,
+ if (uninstallFromAllUsers) null else uninstalledUser
+ )
+ }
+
+ return UninstallUserActionRequired(title, message, appDataSize)
+ }
+
+ /**
+ * Returns whether there is only one "full" user on this device.
+ *
+ * **Note:** On devices that use [headless system user mode]
+ * [android.os.UserManager.isHeadlessSystemUserMode], the system user is not "full",
+ * so it's not be considered in the calculation.
+ */
+ private fun isSingleUser(): Boolean {
+ val userCount = userManager!!.userCount
+ return userCount == 1 || (UserManager.isHeadlessSystemUserMode() && userCount == 2)
+ }
+
+ /**
+ * Returns the type of the user from where an app is being uninstalled. We are concerned with
+ * only USER_TYPE_PROFILE_MANAGED and USER_TYPE_PROFILE_CLONE and whether the user and profile
+ * belong to the same profile group.
+ */
+ private fun getUninstalledUserType(
+ myUserHandle: UserHandle,
+ uninstalledUserHandle: UserHandle
+ ): String? {
+ if (!userManager!!.isSameProfileGroup(myUserHandle, uninstalledUserHandle)) {
+ return null
+ }
+ val customUserManager = context.createContextAsUser(uninstalledUserHandle, 0)
+ .getSystemService(UserManager::class.java)
+ val userTypes =
+ arrayOf(UserManager.USER_TYPE_PROFILE_MANAGED, UserManager.USER_TYPE_PROFILE_CLONE)
+
+ for (userType in userTypes) {
+ if (customUserManager!!.isUserOfType(userType)) {
+ return userType
+ }
+ }
+ return null
+ }
+
+ private fun hasClonedInstance(packageName: String): Boolean {
+ // Check if clone user is present on the device.
+ var cloneUser: UserHandle? = null
+ val profiles = userManager!!.userProfiles
+
+ for (userHandle in profiles) {
+ if (userHandle != UserHandle.SYSTEM && isCloneProfile(userHandle)) {
+ cloneUser = userHandle
+ break
+ }
+ }
+ // Check if another instance of given package exists in clone user profile.
+ return try {
+ cloneUser != null
+ && packageManager.getPackageUidAsUser(
+ packageName, PackageManager.PackageInfoFlags.of(0), cloneUser.identifier
+ ) > 0
+ } catch (e: PackageManager.NameNotFoundException) {
+ false
+ }
+ }
+
+ private fun isCloneProfile(userHandle: UserHandle): Boolean {
+ val customUserManager = context.createContextAsUser(userHandle, 0)
+ .getSystemService(UserManager::class.java)
+ return customUserManager!!.isUserOfType(UserManager.USER_TYPE_PROFILE_CLONE)
+ }
+
+ /**
+ * Get number of bytes of the app data of the package.
+ *
+ * @param pkg The package that might have app data.
+ * @param user The user the package belongs to or `null` if files of all users should
+ * be counted.
+ * @return The number of bytes.
+ */
+ private fun getAppDataSize(pkg: String, user: UserHandle?): Long {
+ if (user != null) {
+ return getAppDataSizeForUser(pkg, user)
+ }
+ // We are uninstalling from all users. Get cumulative app data size for all users.
+ val userHandles = userManager!!.getUserHandles(true)
+ var totalAppDataSize: Long = 0
+ val numUsers = userHandles.size
+ for (i in 0 until numUsers) {
+ totalAppDataSize += getAppDataSizeForUser(pkg, userHandles[i])
+ }
+ return totalAppDataSize
+ }
+
+ /**
+ * Get number of bytes of the app data of the package.
+ *
+ * @param pkg The package that might have app data.
+ * @param user The user the package belongs to
+ * @return The number of bytes.
+ */
+ private fun getAppDataSizeForUser(pkg: String, user: UserHandle): Long {
+ val storageStatsManager = context.getSystemService(StorageStatsManager::class.java)
+ try {
+ val stats = storageStatsManager!!.queryStatsForPackage(
+ packageManager.getApplicationInfo(pkg, 0).storageUuid, pkg, user
+ )
+ return stats.getDataBytes()
+ } catch (e: Exception) {
+ Log.e(LOG_TAG, "Cannot determine amount of app data for $pkg", e)
+ }
+ return 0
+ }
+
+ fun initiateUninstall(keepData: Boolean) {
+ // Get an uninstallId to track results and show a notification on non-TV devices.
+ uninstallId = try {
+ UninstallEventReceiver.addObserver(
+ context, EventResultPersister.GENERATE_NEW_ID, this::handleUninstallResult
+ )
+ } catch (e: OutOfIdsException) {
+ Log.e(LOG_TAG, "Failed to start uninstall", e)
+ handleUninstallResult(
+ PackageInstaller.STATUS_FAILURE,
+ PackageManager.DELETE_FAILED_INTERNAL_ERROR, null, 0
+ )
+ return
+ }
+
+ // TODO: Check with UX whether to show UninstallUninstalling dialog / notification?
+ uninstallResult.value = UninstallUninstalling(targetAppLabel, isClonedApp)
+
+ val uninstallData = Bundle()
+ uninstallData.putInt(EXTRA_UNINSTALL_ID, uninstallId)
+ uninstallData.putString(EXTRA_PACKAGE_NAME, targetPackageName)
+ uninstallData.putBoolean(Intent.EXTRA_UNINSTALL_ALL_USERS, uninstallFromAllUsers)
+ uninstallData.putCharSequence(EXTRA_APP_LABEL, targetAppLabel)
+ uninstallData.putBoolean(EXTRA_IS_CLONE_APP, isClonedApp)
+ Log.i(LOG_TAG, "Uninstalling extras = $uninstallData")
+
+ // Get a PendingIntent for result broadcast and issue an uninstall request
+ val broadcastIntent = Intent(BROADCAST_ACTION)
+ broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, uninstallId)
+ broadcastIntent.setPackage(context.packageName)
+ val pendingIntent = PendingIntent.getBroadcast(
+ context, uninstallId, broadcastIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
+ )
+ if (!startUninstall(
+ targetPackageName!!, uninstalledUser!!, pendingIntent, uninstallFromAllUsers,
+ keepData
+ )
+ ) {
+ handleUninstallResult(
+ PackageInstaller.STATUS_FAILURE,
+ PackageManager.DELETE_FAILED_INTERNAL_ERROR, null, 0
+ )
+ }
+ }
+
+ private fun handleUninstallResult(
+ status: Int,
+ legacyStatus: Int,
+ message: String?,
+ serviceId: Int
+ ) {
+ if (callback != null) {
+ // The caller will be informed about the result via a callback
+ callback!!.onUninstallComplete(targetPackageName!!, legacyStatus, message)
+
+ // Since the caller already received the results, just finish the app at this point
+ uninstallResult.value = null
+ return
+ }
+ val returnResult = intent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)
+ if (returnResult || callingActivity != null) {
+ val intent = Intent()
+ intent.putExtra(Intent.EXTRA_INSTALL_RESULT, legacyStatus)
+ if (status == PackageInstaller.STATUS_SUCCESS) {
+ uninstallResult.setValue(
+ UninstallSuccess(resultIntent = intent, activityResultCode = Activity.RESULT_OK)
+ )
+ } else {
+ uninstallResult.setValue(
+ UninstallFailed(
+ returnResult = true,
+ resultIntent = intent,
+ activityResultCode = Activity.RESULT_FIRST_USER
+ )
+ )
+ }
+ return
+ }
+
+ // Caller did not want the result back. So, we either show a Toast, or a Notification.
+ if (status == PackageInstaller.STATUS_SUCCESS) {
+ val statusMessage = if (isClonedApp) context.getString(
+ R.string.uninstall_done_clone_app, targetAppLabel
+ ) else context.getString(R.string.uninstall_done_app, targetAppLabel)
+ uninstallResult.setValue(
+ UninstallSuccess(activityResultCode = legacyStatus, message = statusMessage)
+ )
+ } else {
+ val uninstallFailureChannel = NotificationChannel(
+ UNINSTALL_FAILURE_CHANNEL,
+ context.getString(R.string.uninstall_failure_notification_channel),
+ NotificationManager.IMPORTANCE_DEFAULT
+ )
+ notificationManager!!.createNotificationChannel(uninstallFailureChannel)
+
+ val uninstallFailedNotification: Notification.Builder =
+ Notification.Builder(context, UNINSTALL_FAILURE_CHANNEL)
+
+ val myUserHandle = Process.myUserHandle()
+ when (legacyStatus) {
+ PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER -> {
+ // Find out if the package is an active admin for some non-current user.
+ val otherBlockingUserHandle =
+ findUserOfDeviceAdmin(myUserHandle, targetPackageName!!)
+ if (otherBlockingUserHandle == null) {
+ Log.d(
+ LOG_TAG, "Uninstall failed because $targetPackageName"
+ + " is a device admin"
+ )
+ addDeviceManagerButton(context, uninstallFailedNotification)
+ setBigText(
+ uninstallFailedNotification, context.getString(
+ R.string.uninstall_failed_device_policy_manager
+ )
+ )
+ } else {
+ Log.d(
+ LOG_TAG, "Uninstall failed because $targetPackageName"
+ + " is a device admin of user $otherBlockingUserHandle"
+ )
+ val userName = context.createContextAsUser(otherBlockingUserHandle, 0)
+ .getSystemService(UserManager::class.java)!!.userName
+ setBigText(
+ uninstallFailedNotification, String.format(
+ context.getString(
+ R.string.uninstall_failed_device_policy_manager_of_user
+ ), userName
+ )
+ )
+ }
+ }
+
+ PackageManager.DELETE_FAILED_OWNER_BLOCKED -> {
+ val otherBlockingUserHandle = findBlockingUser(targetPackageName!!)
+ val isProfileOfOrSame = isProfileOfOrSame(
+ userManager!!, myUserHandle, otherBlockingUserHandle
+ )
+ if (isProfileOfOrSame) {
+ addDeviceManagerButton(context, uninstallFailedNotification)
+ } else {
+ addManageUsersButton(context, uninstallFailedNotification)
+ }
+ var bigText: String? = null
+ if (otherBlockingUserHandle == null) {
+ Log.d(
+ LOG_TAG, "Uninstall failed for $targetPackageName " +
+ "with code $status no blocking user"
+ )
+ } else if (otherBlockingUserHandle === UserHandle.SYSTEM) {
+ bigText = context.getString(R.string.uninstall_blocked_device_owner)
+ } else {
+ bigText = context.getString(
+ if (uninstallFromAllUsers) R.string.uninstall_all_blocked_profile_owner
+ else R.string.uninstall_blocked_profile_owner
+ )
+ }
+ bigText?.let { setBigText(uninstallFailedNotification, it) }
+ }
+
+ else -> {
+ Log.d(
+ LOG_TAG, "Uninstall blocked for $targetPackageName"
+ + " with legacy code $legacyStatus"
+ )
+ }
+ }
+ uninstallFailedNotification.setContentTitle(
+ context.getString(R.string.uninstall_failed_app, targetAppLabel)
+ )
+ uninstallFailedNotification.setOngoing(false)
+ uninstallFailedNotification.setSmallIcon(R.drawable.ic_error)
+
+ uninstallResult.setValue(
+ UninstallFailed(
+ returnResult = false,
+ uninstallNotificationId = uninstallId,
+ uninstallNotification = uninstallFailedNotification.build()
+ )
+ )
+ }
+ }
+
+ /**
+ * @param myUserHandle [UserHandle] of the current user.
+ * @param packageName Name of the package being uninstalled.
+ * @return the [UserHandle] of the user in which a package is a device admin.
+ */
+ private fun findUserOfDeviceAdmin(myUserHandle: UserHandle, packageName: String): UserHandle? {
+ for (otherUserHandle in userManager!!.getUserHandles(true)) {
+ // We only catch the case when the user in question is neither the
+ // current user nor its profile.
+ if (isProfileOfOrSame(userManager, myUserHandle, otherUserHandle)) {
+ continue
+ }
+ val dpm = context.createContextAsUser(otherUserHandle, 0)
+ .getSystemService(DevicePolicyManager::class.java)
+ if (dpm!!.packageHasActiveAdmins(packageName)) {
+ return otherUserHandle
+ }
+ }
+ return null
+ }
+
+ /**
+ *
+ * @param packageName Name of the package being uninstalled.
+ * @return [UserHandle] of the user in which a package is blocked from being uninstalled.
+ */
+ private fun findBlockingUser(packageName: String): UserHandle? {
+ for (otherUserHandle in userManager!!.getUserHandles(true)) {
+ // TODO (b/307399586): Add a negation when the logic of the method is fixed
+ if (packageManager.canUserUninstall(packageName, otherUserHandle)) {
+ return otherUserHandle
+ }
+ }
+ return null
+ }
+
+ /**
+ * Set big text for the notification.
+ *
+ * @param builder The builder of the notification
+ * @param text The text to set.
+ */
+ private fun setBigText(
+ builder: Notification.Builder,
+ text: CharSequence
+ ) {
+ builder.setStyle(Notification.BigTextStyle().bigText(text))
+ }
+
+ /**
+ * Add a button to the notification that links to the user management.
+ *
+ * @param context The context the notification is created in
+ * @param builder The builder of the notification
+ */
+ private fun addManageUsersButton(
+ context: Context,
+ builder: Notification.Builder
+ ) {
+ builder.addAction(
+ Notification.Action.Builder(
+ Icon.createWithResource(context, R.drawable.ic_settings_multiuser),
+ context.getString(R.string.manage_users),
+ PendingIntent.getActivity(
+ context, 0, getUserSettingsIntent(),
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+ )
+ .build()
+ )
+ }
+
+ private fun getUserSettingsIntent(): Intent {
+ val intent = Intent(Settings.ACTION_USER_SETTINGS)
+ intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY or Intent.FLAG_ACTIVITY_NEW_TASK)
+ return intent
+ }
+
+ /**
+ * Add a button to the notification that links to the device policy management.
+ *
+ * @param context The context the notification is created in
+ * @param builder The builder of the notification
+ */
+ private fun addDeviceManagerButton(
+ context: Context,
+ builder: Notification.Builder
+ ) {
+ builder.addAction(
+ Notification.Action.Builder(
+ Icon.createWithResource(context, R.drawable.ic_lock),
+ context.getString(R.string.manage_device_administrators),
+ PendingIntent.getActivity(
+ context, 0, getDeviceManagerIntent(),
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+ )
+ .build()
+ )
+ }
+
+ private fun getDeviceManagerIntent(): Intent {
+ val intent = Intent()
+ intent.setClassName(
+ "com.android.settings",
+ "com.android.settings.Settings\$DeviceAdminSettingsActivity"
+ )
+ intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY or Intent.FLAG_ACTIVITY_NEW_TASK)
+ return intent
+ }
+
+ /**
+ * Starts an uninstall for the given package.
+ *
+ * @return `true` if there was no exception while uninstalling. This does not represent
+ * the result of the uninstall. Result will be made available in [handleUninstallResult]
+ */
+ private fun startUninstall(
+ packageName: String,
+ targetUser: UserHandle,
+ pendingIntent: PendingIntent,
+ uninstallFromAllUsers: Boolean,
+ keepData: Boolean
+ ): Boolean {
+ var flags = if (uninstallFromAllUsers) PackageManager.DELETE_ALL_USERS else 0
+ flags = flags or if (keepData) PackageManager.DELETE_KEEP_DATA else 0
+
+ return try {
+ context.createContextAsUser(targetUser, 0)
+ .packageManager.packageInstaller.uninstall(
+ VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST),
+ flags, pendingIntent.intentSender
+ )
+ true
+ } catch (e: IllegalArgumentException) {
+ Log.e(LOG_TAG, "Failed to uninstall", e)
+ false
+ }
+ }
+
+ fun cancelInstall() {
+ if (callback != null) {
+ callback!!.onUninstallComplete(
+ targetPackageName!!,
+ PackageManager.DELETE_FAILED_ABORTED, "Cancelled by user"
+ )
+ }
+ }
+
+ companion object {
+ private val LOG_TAG = UninstallRepository::class.java.simpleName
+ private const val UNINSTALL_FAILURE_CHANNEL = "uninstall_failure"
+ private const val BROADCAST_ACTION = "com.android.packageinstaller.ACTION_UNINSTALL_COMMIT"
+ private const val EXTRA_UNINSTALL_ID = "com.android.packageinstaller.extra.UNINSTALL_ID"
+ private const val EXTRA_APP_LABEL = "com.android.packageinstaller.extra.APP_LABEL"
+ private const val EXTRA_IS_CLONE_APP = "com.android.packageinstaller.extra.IS_CLONE_APP"
+ private const val EXTRA_PACKAGE_NAME =
+ "com.android.packageinstaller.extra.EXTRA_PACKAGE_NAME"
+ }
+
+ class CallerInfo(val activityName: String?, val uid: Int)
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallStages.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallStages.kt
new file mode 100644
index 000000000000..f086209fe498
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallStages.kt
@@ -0,0 +1,112 @@
+/*
+ * 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
+ *
+ * https://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.packageinstaller.v2.model
+
+import android.app.Activity
+import android.app.Notification
+import android.content.Intent
+import com.android.packageinstaller.R
+
+sealed class UninstallStage(val stageCode: Int) {
+
+ companion object {
+ const val STAGE_DEFAULT = -1
+ const val STAGE_ABORTED = 0
+ const val STAGE_READY = 1
+ const val STAGE_USER_ACTION_REQUIRED = 2
+ const val STAGE_UNINSTALLING = 3
+ const val STAGE_SUCCESS = 4
+ const val STAGE_FAILED = 5
+ }
+}
+
+class UninstallReady : UninstallStage(STAGE_READY)
+
+data class UninstallUserActionRequired(
+ val title: String? = null,
+ val message: String? = null,
+ val appDataSize: Long = 0
+) : UninstallStage(STAGE_USER_ACTION_REQUIRED)
+
+data class UninstallUninstalling(val appLabel: CharSequence, val isCloneUser: Boolean) :
+ UninstallStage(STAGE_UNINSTALLING)
+
+data class UninstallSuccess(
+ val resultIntent: Intent? = null,
+ val activityResultCode: Int = 0,
+ val message: String? = null,
+) : UninstallStage(STAGE_SUCCESS)
+
+data class UninstallFailed(
+ val returnResult: Boolean,
+ /**
+ * If the caller wants the result back, the intent will hold the uninstall failure status code
+ * and legacy code.
+ */
+ val resultIntent: Intent? = null,
+ val activityResultCode: Int = Activity.RESULT_CANCELED,
+ /**
+ * ID used to show [uninstallNotification]
+ */
+ val uninstallNotificationId: Int? = null,
+ /**
+ * When the user does not request a result back, this notification will be shown indicating the
+ * reason for uninstall failure.
+ */
+ val uninstallNotification: Notification? = null,
+) : UninstallStage(STAGE_FAILED) {
+
+ init {
+ if (uninstallNotification != null && uninstallNotificationId == null) {
+ throw IllegalArgumentException(
+ "uninstallNotification cannot be set without uninstallNotificationId"
+ )
+ }
+ }
+}
+
+data class UninstallAborted(val abortReason: Int) : UninstallStage(STAGE_ABORTED) {
+
+ var dialogTitleResource = 0
+ var dialogTextResource = 0
+ val activityResultCode = Activity.RESULT_FIRST_USER
+
+ init {
+ when (abortReason) {
+ ABORT_REASON_APP_UNAVAILABLE -> {
+ dialogTitleResource = R.string.app_not_found_dlg_title
+ dialogTextResource = R.string.app_not_found_dlg_text
+ }
+
+ ABORT_REASON_USER_NOT_ALLOWED -> {
+ dialogTitleResource = 0
+ dialogTextResource = R.string.user_is_not_allowed_dlg_text
+ }
+
+ else -> {
+ dialogTitleResource = 0
+ dialogTextResource = R.string.generic_error_dlg_text
+ }
+ }
+ }
+
+ companion object {
+ const val ABORT_REASON_GENERIC_ERROR = 0
+ const val ABORT_REASON_APP_UNAVAILABLE = 1
+ const val ABORT_REASON_USER_NOT_ALLOWED = 2
+ }
+}
+
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallAborted.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallAborted.java
deleted file mode 100644
index 520b6c573acf..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallAborted.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.model.installstagedata;
-
-
-import android.app.Activity;
-import android.content.Intent;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-public class InstallAborted extends InstallStage {
-
- public static final int ABORT_REASON_INTERNAL_ERROR = 0;
- public static final int ABORT_REASON_POLICY = 1;
- public static final int ABORT_REASON_DONE = 2;
- public static final int DLG_PACKAGE_ERROR = 1;
- private final int mStage = InstallStage.STAGE_ABORTED;
- private final int mAbortReason;
-
- /**
- * It will hold the restriction name, when the restriction was enforced by the system, and not
- * a device admin.
- */
- @NonNull
- private final String mMessage;
- /**
- * <p>If abort reason is ABORT_REASON_POLICY, then this will hold the Intent
- * to display a support dialog when a feature was disabled by an admin. It will be
- * {@code null} if the feature is disabled by the system. In this case, the restriction name
- * will be set in {@link #mMessage} </p>
- *
- * <p>If the abort reason is ABORT_REASON_INTERNAL_ERROR, it <b>may</b> hold an
- * intent to be sent as a result to the calling activity.</p>
- */
- @Nullable
- private final Intent mIntent;
- private final int mErrorDialogType;
- private final int mActivityResultCode;
-
- private InstallAborted(int reason, @NonNull String message, @Nullable Intent intent,
- int activityResultCode, int errorDialogType) {
- mAbortReason = reason;
- mMessage = message;
- mIntent = intent;
- mErrorDialogType = errorDialogType;
- mActivityResultCode = activityResultCode;
- }
-
- public int getAbortReason() {
- return mAbortReason;
- }
-
- @NonNull
- public String getMessage() {
- return mMessage;
- }
-
- @Nullable
- public Intent getResultIntent() {
- return mIntent;
- }
-
- public int getErrorDialogType() {
- return mErrorDialogType;
- }
-
- public int getActivityResultCode() {
- return mActivityResultCode;
- }
-
- @Override
- public int getStageCode() {
- return mStage;
- }
-
- public static class Builder {
-
- private final int mAbortReason;
- private String mMessage = "";
- private Intent mIntent = null;
- private int mActivityResultCode = Activity.RESULT_CANCELED;
- private int mErrorDialogType;
-
- public Builder(int reason) {
- mAbortReason = reason;
- }
-
- public Builder setMessage(@NonNull String message) {
- mMessage = message;
- return this;
- }
-
- public Builder setResultIntent(@NonNull Intent intent) {
- mIntent = intent;
- return this;
- }
-
- public Builder setErrorDialogType(int dialogType) {
- mErrorDialogType = dialogType;
- return this;
- }
-
- public Builder setActivityResultCode(int resultCode) {
- mActivityResultCode = resultCode;
- return this;
- }
-
- public InstallAborted build() {
- return new InstallAborted(mAbortReason, mMessage, mIntent, mActivityResultCode,
- mErrorDialogType);
- }
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallFailed.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallFailed.java
deleted file mode 100644
index 67e169036551..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallFailed.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.model.installstagedata;
-
-import android.graphics.drawable.Drawable;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import com.android.packageinstaller.v2.model.PackageUtil.AppSnippet;
-
-public class InstallFailed extends InstallStage {
-
- private final int mStage = InstallStage.STAGE_FAILED;
- @NonNull
- private final AppSnippet mAppSnippet;
- private final int mStatusCode;
- private final int mLegacyCode;
- @Nullable
- private final String mMessage;
-
- public InstallFailed(@NonNull AppSnippet appSnippet, int statusCode, int legacyCode,
- @Nullable String message) {
- mAppSnippet = appSnippet;
- mLegacyCode = statusCode;
- mStatusCode = legacyCode;
- mMessage = message;
- }
-
- @Override
- public int getStageCode() {
- return mStage;
- }
-
- @NonNull
- public Drawable getAppIcon() {
- return mAppSnippet.getIcon();
- }
-
- @NonNull
- public String getAppLabel() {
- return (String) mAppSnippet.getLabel();
- }
-
- public int getStatusCode() {
- return mStatusCode;
- }
-
- public int getLegacyCode() {
- return mLegacyCode;
- }
-
- @Nullable
- public String getMessage() {
- return mMessage;
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallInstalling.java
deleted file mode 100644
index efd4947f712f..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallInstalling.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.model.installstagedata;
-
-import android.graphics.drawable.Drawable;
-import androidx.annotation.NonNull;
-import com.android.packageinstaller.v2.model.PackageUtil.AppSnippet;
-
-public class InstallInstalling extends InstallStage {
-
- private final int mStage = InstallStage.STAGE_INSTALLING;
- @NonNull
- private final AppSnippet mAppSnippet;
-
- public InstallInstalling(@NonNull AppSnippet appSnippet) {
- mAppSnippet = appSnippet;
- }
-
- @Override
- public int getStageCode() {
- return mStage;
- }
-
- @NonNull
- public Drawable getAppIcon() {
- return mAppSnippet.getIcon();
- }
-
- @NonNull
- public String getAppLabel() {
- return (String) mAppSnippet.getLabel();
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStage.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStage.java
deleted file mode 100644
index f91e64bdc326..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStage.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.model.installstagedata;
-
-public abstract class InstallStage {
-
- public static final int STAGE_DEFAULT = -1;
- public static final int STAGE_ABORTED = 0;
- public static final int STAGE_STAGING = 1;
- public static final int STAGE_READY = 2;
- public static final int STAGE_USER_ACTION_REQUIRED = 3;
- public static final int STAGE_INSTALLING = 4;
- public static final int STAGE_SUCCESS = 5;
- public static final int STAGE_FAILED = 6;
-
- /**
- * @return the integer value representing current install stage.
- */
- public abstract int getStageCode();
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallSuccess.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallSuccess.java
deleted file mode 100644
index da482564c505..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallSuccess.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.model.installstagedata;
-
-import android.content.Intent;
-import android.graphics.drawable.Drawable;
-import androidx.annotation.NonNull;
-import com.android.packageinstaller.v2.model.PackageUtil.AppSnippet;
-
-public class InstallSuccess extends InstallStage {
-
- private final int mStage = InstallStage.STAGE_SUCCESS;
-
- @NonNull
- private final AppSnippet mAppSnippet;
- private final boolean mShouldReturnResult;
- /**
- * <p>If the caller is requesting a result back, this will hold the Intent with
- * EXTRA_INSTALL_RESULT set to INSTALL_SUCCEEDED which is sent back to the caller.</p>
- * <p>If the caller doesn't want the result back, this will hold the Intent that launches
- * the newly installed / updated app.</p>
- */
- @NonNull
- private final Intent mResultIntent;
-
- public InstallSuccess(@NonNull AppSnippet appSnippet, boolean shouldReturnResult,
- @NonNull Intent launcherIntent) {
- mAppSnippet = appSnippet;
- mShouldReturnResult = shouldReturnResult;
- mResultIntent = launcherIntent;
- }
-
- @Override
- public int getStageCode() {
- return mStage;
- }
-
- @NonNull
- public Drawable getAppIcon() {
- return mAppSnippet.getIcon();
- }
-
- @NonNull
- public String getAppLabel() {
- return (String) mAppSnippet.getLabel();
- }
-
- public boolean shouldReturnResult() {
- return mShouldReturnResult;
- }
-
- @NonNull
- public Intent getResultIntent() {
- return mResultIntent;
- }
-
- public static class Builder {
-
- private final AppSnippet mAppSnippet;
- private boolean mShouldReturnResult;
- private Intent mLauncherIntent;
-
- public Builder(@NonNull AppSnippet appSnippet) {
- mAppSnippet = appSnippet;
- }
-
- public Builder setShouldReturnResult(boolean returnResult) {
- mShouldReturnResult = returnResult;
- return this;
- }
-
- public Builder setResultIntent(@NonNull Intent intent) {
- mLauncherIntent = intent;
- return this;
- }
-
- public InstallSuccess build() {
- return new InstallSuccess(mAppSnippet, mShouldReturnResult, mLauncherIntent);
- }
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallUserActionRequired.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallUserActionRequired.java
deleted file mode 100644
index 08a7487c69d3..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallUserActionRequired.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.model.installstagedata;
-
-import android.graphics.drawable.Drawable;
-import androidx.annotation.Nullable;
-import com.android.packageinstaller.v2.model.PackageUtil.AppSnippet;
-
-public class InstallUserActionRequired extends InstallStage {
-
- public static final int USER_ACTION_REASON_UNKNOWN_SOURCE = 0;
- public static final int USER_ACTION_REASON_ANONYMOUS_SOURCE = 1;
- public static final int USER_ACTION_REASON_INSTALL_CONFIRMATION = 2;
- private final int mStage = InstallStage.STAGE_USER_ACTION_REQUIRED;
- private final int mActionReason;
- @Nullable
- private final AppSnippet mAppSnippet;
- private final boolean mIsAppUpdating;
- @Nullable
- private final String mDialogMessage;
-
- public InstallUserActionRequired(int actionReason, @Nullable AppSnippet appSnippet,
- boolean isUpdating, @Nullable String dialogMessage) {
- mActionReason = actionReason;
- mAppSnippet = appSnippet;
- mIsAppUpdating = isUpdating;
- mDialogMessage = dialogMessage;
- }
-
- @Override
- public int getStageCode() {
- return mStage;
- }
-
- @Nullable
- public Drawable getAppIcon() {
- return mAppSnippet != null ? mAppSnippet.getIcon() : null;
- }
-
- @Nullable
- public String getAppLabel() {
- return mAppSnippet != null ? (String) mAppSnippet.getLabel() : null;
- }
-
- public boolean isAppUpdating() {
- return mIsAppUpdating;
- }
-
- @Nullable
- public String getDialogMessage() {
- return mDialogMessage;
- }
-
- public int getActionReason() {
- return mActionReason;
- }
-
- public static class Builder {
-
- private final int mActionReason;
- private final AppSnippet mAppSnippet;
- private boolean mIsAppUpdating;
- private String mDialogMessage;
-
- public Builder(int actionReason, @Nullable AppSnippet appSnippet) {
- mActionReason = actionReason;
- mAppSnippet = appSnippet;
- }
-
- public Builder setAppUpdating(boolean isUpdating) {
- mIsAppUpdating = isUpdating;
- return this;
- }
-
- public Builder setDialogMessage(@Nullable String message) {
- mDialogMessage = message;
- return this;
- }
-
- public InstallUserActionRequired build() {
- return new InstallUserActionRequired(mActionReason, mAppSnippet, mIsAppUpdating,
- mDialogMessage);
- }
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallAborted.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallAborted.java
deleted file mode 100644
index 9aea6b18214b..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallAborted.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://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.packageinstaller.v2.model.uninstallstagedata;
-
-import android.app.Activity;
-import com.android.packageinstaller.R;
-
-public class UninstallAborted extends UninstallStage {
-
- public static final int ABORT_REASON_GENERIC_ERROR = 0;
- public static final int ABORT_REASON_APP_UNAVAILABLE = 1;
- public static final int ABORT_REASON_USER_NOT_ALLOWED = 2;
- private final int mStage = UninstallStage.STAGE_ABORTED;
- private final int mAbortReason;
- private final int mDialogTitleResource;
- private final int mDialogTextResource;
- private final int mActivityResultCode = Activity.RESULT_FIRST_USER;
-
- public UninstallAborted(int abortReason) {
- mAbortReason = abortReason;
- switch (abortReason) {
- case ABORT_REASON_APP_UNAVAILABLE -> {
- mDialogTitleResource = R.string.app_not_found_dlg_title;
- mDialogTextResource = R.string.app_not_found_dlg_text;
- }
- case ABORT_REASON_USER_NOT_ALLOWED -> {
- mDialogTitleResource = 0;
- mDialogTextResource = R.string.user_is_not_allowed_dlg_text;
- }
- default -> {
- mDialogTitleResource = 0;
- mDialogTextResource = R.string.generic_error_dlg_text;
- }
- }
- }
-
- public int getAbortReason() {
- return mAbortReason;
- }
-
- public int getActivityResultCode() {
- return mActivityResultCode;
- }
-
- public int getDialogTitleResource() {
- return mDialogTitleResource;
- }
-
- public int getDialogTextResource() {
- return mDialogTextResource;
- }
-
- @Override
- public int getStageCode() {
- return mStage;
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallFailed.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallFailed.java
deleted file mode 100644
index 6ed8883570e3..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallFailed.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://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.packageinstaller.v2.model.uninstallstagedata;
-
-import android.app.Activity;
-import android.app.Notification;
-import android.content.Intent;
-
-public class UninstallFailed extends UninstallStage {
-
- private final int mStage = UninstallStage.STAGE_FAILED;
- private final boolean mReturnResult;
- /**
- * If the caller wants the result back, the intent will hold the uninstall failure status code
- * and legacy code.
- */
- private final Intent mResultIntent;
- /**
- * When the user does not request a result back, this notification will be shown indicating the
- * reason for uninstall failure.
- */
- private final Notification mUninstallNotification;
- /**
- * ID used to show {@link #mUninstallNotification}
- */
- private final int mUninstallId;
- private final int mActivityResultCode;
-
- public UninstallFailed(boolean returnResult, Intent resultIntent, int activityResultCode,
- int uninstallId, Notification uninstallNotification) {
- mReturnResult = returnResult;
- mResultIntent = resultIntent;
- mActivityResultCode = activityResultCode;
- mUninstallId = uninstallId;
- mUninstallNotification = uninstallNotification;
- }
-
- public boolean returnResult() {
- return mReturnResult;
- }
-
- public Intent getResultIntent() {
- return mResultIntent;
- }
-
- public int getActivityResultCode() {
- return mActivityResultCode;
- }
-
- public Notification getUninstallNotification() {
- return mUninstallNotification;
- }
-
- public int getUninstallId() {
- return mUninstallId;
- }
-
- @Override
- public int getStageCode() {
- return mStage;
- }
-
- public static class Builder {
-
- private final boolean mReturnResult;
- private int mActivityResultCode = Activity.RESULT_CANCELED;
- /**
- * See {@link UninstallFailed#mResultIntent}
- */
- private Intent mResultIntent = null;
- /**
- * See {@link UninstallFailed#mUninstallNotification}
- */
- private Notification mUninstallNotification;
- /**
- * See {@link UninstallFailed#mUninstallId}
- */
- private int mUninstallId;
-
- public Builder(boolean returnResult) {
- mReturnResult = returnResult;
- }
-
- public Builder setUninstallNotification(int uninstallId, Notification notification) {
- mUninstallId = uninstallId;
- mUninstallNotification = notification;
- return this;
- }
-
- public Builder setResultIntent(Intent intent) {
- mResultIntent = intent;
- return this;
- }
-
- public Builder setActivityResultCode(int resultCode) {
- mActivityResultCode = resultCode;
- return this;
- }
-
- public UninstallFailed build() {
- return new UninstallFailed(mReturnResult, mResultIntent, mActivityResultCode,
- mUninstallId, mUninstallNotification);
- }
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallReady.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallReady.java
deleted file mode 100644
index 0108cb471b5a..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallReady.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://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.packageinstaller.v2.model.uninstallstagedata;
-
-public class UninstallReady extends UninstallStage {
-
- private final int mStage = UninstallStage.STAGE_READY;
-
- @Override
- public int getStageCode() {
- return mStage;
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallSuccess.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallSuccess.java
deleted file mode 100644
index 5df6b020cef5..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallSuccess.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://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.packageinstaller.v2.model.uninstallstagedata;
-
-import android.content.Intent;
-
-public class UninstallSuccess extends UninstallStage {
-
- private final int mStage = UninstallStage.STAGE_SUCCESS;
- private final String mMessage;
- private final Intent mResultIntent;
- private final int mActivityResultCode;
-
- public UninstallSuccess(Intent resultIntent, int activityResultCode, String message) {
- mResultIntent = resultIntent;
- mActivityResultCode = activityResultCode;
- mMessage = message;
- }
-
- public String getMessage() {
- return mMessage;
- }
-
- public Intent getResultIntent() {
- return mResultIntent;
- }
-
- public int getActivityResultCode() {
- return mActivityResultCode;
- }
-
- @Override
- public int getStageCode() {
- return mStage;
- }
-
- public static class Builder {
-
- private Intent mResultIntent;
- private int mActivityResultCode;
- private String mMessage;
-
- public Builder() {
- }
-
- public Builder setResultIntent(Intent intent) {
- mResultIntent = intent;
- return this;
- }
-
- public Builder setActivityResultCode(int resultCode) {
- mActivityResultCode = resultCode;
- return this;
- }
-
- public Builder setMessage(String message) {
- mMessage = message;
- return this;
- }
-
- public UninstallSuccess build() {
- return new UninstallSuccess(mResultIntent, mActivityResultCode, mMessage);
- }
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUninstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUninstalling.java
deleted file mode 100644
index f5156cb676e9..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUninstalling.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://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.packageinstaller.v2.model.uninstallstagedata;
-
-public class UninstallUninstalling extends UninstallStage {
-
- private final int mStage = UninstallStage.STAGE_UNINSTALLING;
-
- private final CharSequence mAppLabel;
- private final boolean mIsCloneUser;
-
- public UninstallUninstalling(CharSequence appLabel, boolean isCloneUser) {
- mAppLabel = appLabel;
- mIsCloneUser = isCloneUser;
- }
-
- public CharSequence getAppLabel() {
- return mAppLabel;
- }
-
- public boolean isCloneUser() {
- return mIsCloneUser;
- }
-
- @Override
- public int getStageCode() {
- return mStage;
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUserActionRequired.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUserActionRequired.java
deleted file mode 100644
index b6001493ade9..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallUserActionRequired.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://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.packageinstaller.v2.model.uninstallstagedata;
-
-public class UninstallUserActionRequired extends UninstallStage {
-
- private final int mStage = UninstallStage.STAGE_USER_ACTION_REQUIRED;
- private final String mTitle;
- private final String mMessage;
- private final long mAppDataSize;
-
- public UninstallUserActionRequired(String title, String message, long appDataSize) {
- mTitle = title;
- mMessage = message;
- mAppDataSize = appDataSize;
- }
-
- public String getTitle() {
- return mTitle;
- }
-
- public String getMessage() {
- return mMessage;
- }
-
- public long getAppDataSize() {
- return mAppDataSize;
- }
-
- @Override
- public int getStageCode() {
- return mStage;
- }
-
- public static class Builder {
-
- private String mTitle;
- private String mMessage;
- private long mAppDataSize = 0;
-
- public Builder setTitle(String title) {
- mTitle = title;
- return this;
- }
-
- public Builder setMessage(String message) {
- mMessage = message;
- return this;
- }
-
- public Builder setAppDataSize(long appDataSize) {
- mAppDataSize = appDataSize;
- return this;
- }
-
- public UninstallUserActionRequired build() {
- return new UninstallUserActionRequired(mTitle, mMessage, mAppDataSize);
- }
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.kt
index fdb024ffc23e..c109fc673ec4 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.kt
@@ -14,25 +14,28 @@
* limitations under the License.
*/
-package com.android.packageinstaller.v2.ui;
+package com.android.packageinstaller.v2.ui
-import android.content.Intent;
+import android.content.Intent
-public interface InstallActionListener {
+interface InstallActionListener {
+ /**
+ * Method to handle a positive response from the user.
+ */
+ fun onPositiveResponse(reasonCode: Int)
/**
- * Method to handle a positive response from the user
+ * Method to dispatch intent for toggling "install from unknown sources" setting for a package.
*/
- void onPositiveResponse(int stageCode);
+ fun sendUnknownAppsIntent(sourcePackageName: String)
/**
- * Method to dispatch intent for toggling "install from unknown sources" setting for a package
+ * Method to handle a negative response from the user.
*/
- void sendUnknownAppsIntent(String packageName);
+ fun onNegativeResponse(stageCode: Int)
/**
- * Method to handle a negative response from the user
+ * Launch the intent to open the newly installed / updated app.
*/
- void onNegativeResponse(int stageCode);
- void openInstalledApp(Intent intent);
+ fun openInstalledApp(intent: Intent?)
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.java
deleted file mode 100644
index d06b4b3b1336..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.java
+++ /dev/null
@@ -1,354 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.ui;
-
-import static android.content.Intent.CATEGORY_LAUNCHER;
-import static android.content.Intent.FLAG_ACTIVITY_NO_HISTORY;
-import static android.os.Process.INVALID_UID;
-import static com.android.packageinstaller.v2.model.InstallRepository.EXTRA_STAGED_SESSION_ID;
-
-import android.app.Activity;
-import android.app.AppOpsManager;
-import android.content.ActivityNotFoundException;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.util.Log;
-import android.view.Window;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.DialogFragment;
-import androidx.fragment.app.FragmentActivity;
-import androidx.fragment.app.FragmentManager;
-import androidx.lifecycle.ViewModelProvider;
-import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.InstallRepository;
-import com.android.packageinstaller.v2.model.InstallRepository.CallerInfo;
-import com.android.packageinstaller.v2.model.installstagedata.InstallAborted;
-import com.android.packageinstaller.v2.model.installstagedata.InstallFailed;
-import com.android.packageinstaller.v2.model.installstagedata.InstallInstalling;
-import com.android.packageinstaller.v2.model.installstagedata.InstallStage;
-import com.android.packageinstaller.v2.model.installstagedata.InstallSuccess;
-import com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired;
-import com.android.packageinstaller.v2.ui.fragments.AnonymousSourceFragment;
-import com.android.packageinstaller.v2.ui.fragments.ExternalSourcesBlockedFragment;
-import com.android.packageinstaller.v2.ui.fragments.InstallConfirmationFragment;
-import com.android.packageinstaller.v2.ui.fragments.InstallFailedFragment;
-import com.android.packageinstaller.v2.ui.fragments.InstallInstallingFragment;
-import com.android.packageinstaller.v2.ui.fragments.InstallStagingFragment;
-import com.android.packageinstaller.v2.ui.fragments.InstallSuccessFragment;
-import com.android.packageinstaller.v2.ui.fragments.SimpleErrorFragment;
-import com.android.packageinstaller.v2.viewmodel.InstallViewModel;
-import com.android.packageinstaller.v2.viewmodel.InstallViewModelFactory;
-import java.util.ArrayList;
-import java.util.List;
-
-public class InstallLaunch extends FragmentActivity implements InstallActionListener {
-
- public static final String EXTRA_CALLING_PKG_UID =
- InstallLaunch.class.getPackageName() + ".callingPkgUid";
- public static final String EXTRA_CALLING_PKG_NAME =
- InstallLaunch.class.getPackageName() + ".callingPkgName";
- private static final String TAG = InstallLaunch.class.getSimpleName();
- private static final String TAG_DIALOG = "dialog";
- private final int REQUEST_TRUST_EXTERNAL_SOURCE = 1;
- private final boolean mLocalLOGV = false;
- /**
- * A collection of unknown sources listeners that are actively listening for app ops mode
- * changes
- */
- private final List<UnknownSourcesListener> mActiveUnknownSourcesListeners = new ArrayList<>(1);
- private InstallViewModel mInstallViewModel;
- private InstallRepository mInstallRepository;
- private FragmentManager mFragmentManager;
- private AppOpsManager mAppOpsManager;
-
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- this.requestWindowFeature(Window.FEATURE_NO_TITLE);
-
- mFragmentManager = getSupportFragmentManager();
- mAppOpsManager = getSystemService(AppOpsManager.class);
-
- mInstallRepository = new InstallRepository(getApplicationContext());
- mInstallViewModel = new ViewModelProvider(this,
- new InstallViewModelFactory(this.getApplication(), mInstallRepository)).get(
- InstallViewModel.class);
-
- Intent intent = getIntent();
- CallerInfo info = new CallerInfo(
- intent.getStringExtra(EXTRA_CALLING_PKG_NAME),
- intent.getIntExtra(EXTRA_CALLING_PKG_UID, INVALID_UID));
- mInstallViewModel.preprocessIntent(intent, info);
-
- mInstallViewModel.getCurrentInstallStage().observe(this, this::onInstallStageChange);
- }
-
- /**
- * Main controller of the UI. This method shows relevant dialogs based on the install stage
- */
- private void onInstallStageChange(InstallStage installStage) {
- switch (installStage.getStageCode()) {
- case InstallStage.STAGE_STAGING -> {
- InstallStagingFragment stagingDialog = new InstallStagingFragment();
- showDialogInner(stagingDialog);
- mInstallViewModel.getStagingProgress().observe(this, stagingDialog::setProgress);
- }
- case InstallStage.STAGE_ABORTED -> {
- InstallAborted aborted = (InstallAborted) installStage;
- switch (aborted.getAbortReason()) {
- // TODO: check if any dialog is to be shown for ABORT_REASON_INTERNAL_ERROR
- case InstallAborted.ABORT_REASON_DONE,
- InstallAborted.ABORT_REASON_INTERNAL_ERROR ->
- setResult(aborted.getActivityResultCode(), aborted.getResultIntent(), true);
- case InstallAborted.ABORT_REASON_POLICY -> showPolicyRestrictionDialog(aborted);
- default -> setResult(RESULT_CANCELED, null, true);
- }
- }
- case InstallStage.STAGE_USER_ACTION_REQUIRED -> {
- InstallUserActionRequired uar = (InstallUserActionRequired) installStage;
- switch (uar.getActionReason()) {
- case InstallUserActionRequired.USER_ACTION_REASON_INSTALL_CONFIRMATION -> {
- InstallConfirmationFragment actionDialog =
- new InstallConfirmationFragment(uar);
- showDialogInner(actionDialog);
- }
- case InstallUserActionRequired.USER_ACTION_REASON_UNKNOWN_SOURCE -> {
- ExternalSourcesBlockedFragment externalSourceDialog =
- new ExternalSourcesBlockedFragment(uar);
- showDialogInner(externalSourceDialog);
- }
- case InstallUserActionRequired.USER_ACTION_REASON_ANONYMOUS_SOURCE -> {
- AnonymousSourceFragment anonymousSourceDialog =
- new AnonymousSourceFragment();
- showDialogInner(anonymousSourceDialog);
- }
- }
- }
- case InstallStage.STAGE_INSTALLING -> {
- InstallInstalling installing = (InstallInstalling) installStage;
- InstallInstallingFragment installingDialog =
- new InstallInstallingFragment(installing);
- showDialogInner(installingDialog);
- }
- case InstallStage.STAGE_SUCCESS -> {
- InstallSuccess success = (InstallSuccess) installStage;
- if (success.shouldReturnResult()) {
- Intent successIntent = success.getResultIntent();
- setResult(Activity.RESULT_OK, successIntent, true);
- } else {
- InstallSuccessFragment successFragment = new InstallSuccessFragment(success);
- showDialogInner(successFragment);
- }
- }
- case InstallStage.STAGE_FAILED -> {
- InstallFailed failed = (InstallFailed) installStage;
- InstallFailedFragment failedDialog = new InstallFailedFragment(failed);
- showDialogInner(failedDialog);
- }
- default -> {
- Log.d(TAG, "Unimplemented stage: " + installStage.getStageCode());
- showDialogInner(null);
- }
- }
- }
-
- private void showPolicyRestrictionDialog(InstallAborted aborted) {
- String restriction = aborted.getMessage();
- Intent adminSupportIntent = aborted.getResultIntent();
- boolean shouldFinish;
-
- // If the given restriction is set by an admin, display information about the
- // admin enforcing the restriction for the affected user. If not enforced by the admin,
- // show the system dialog.
- if (adminSupportIntent != null) {
- if (mLocalLOGV) {
- Log.i(TAG, "Restriction set by admin, starting " + adminSupportIntent);
- }
- startActivity(adminSupportIntent);
- // Finish the package installer app since the next dialog will not be shown by this app
- shouldFinish = true;
- } else {
- if (mLocalLOGV) {
- Log.i(TAG, "Restriction set by system: " + restriction);
- }
- DialogFragment blockedByPolicyDialog = createDevicePolicyRestrictionDialog(restriction);
- // Don't finish the package installer app since the next dialog
- // will be shown by this app
- shouldFinish = false;
- showDialogInner(blockedByPolicyDialog);
- }
- setResult(RESULT_CANCELED, null, shouldFinish);
- }
-
- /**
- * Create a new dialog based on the install restriction enforced.
- *
- * @param restriction The restriction to create the dialog for
- * @return The dialog
- */
- private DialogFragment createDevicePolicyRestrictionDialog(String restriction) {
- if (mLocalLOGV) {
- Log.i(TAG, "createDialog(" + restriction + ")");
- }
- return switch (restriction) {
- case UserManager.DISALLOW_INSTALL_APPS ->
- new SimpleErrorFragment(R.string.install_apps_user_restriction_dlg_text);
- case UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
- UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY ->
- new SimpleErrorFragment(R.string.unknown_apps_user_restriction_dlg_text);
- default -> null;
- };
- }
-
- /**
- * Replace any visible dialog by the dialog returned by InstallRepository
- *
- * @param newDialog The new dialog to display
- */
- private void showDialogInner(@Nullable DialogFragment newDialog) {
- DialogFragment currentDialog = (DialogFragment) mFragmentManager.findFragmentByTag(
- TAG_DIALOG);
- if (currentDialog != null) {
- currentDialog.dismissAllowingStateLoss();
- }
- if (newDialog != null) {
- newDialog.show(mFragmentManager, TAG_DIALOG);
- }
- }
-
- public void setResult(int resultCode, Intent data, boolean shouldFinish) {
- super.setResult(resultCode, data);
- if (shouldFinish) {
- finish();
- }
- }
-
- @Override
- public void onPositiveResponse(int reasonCode) {
- switch (reasonCode) {
- case InstallUserActionRequired.USER_ACTION_REASON_ANONYMOUS_SOURCE ->
- mInstallViewModel.forcedSkipSourceCheck();
- case InstallUserActionRequired.USER_ACTION_REASON_INSTALL_CONFIRMATION ->
- mInstallViewModel.initiateInstall();
- }
- }
-
- @Override
- public void onNegativeResponse(int stageCode) {
- if (stageCode == InstallStage.STAGE_USER_ACTION_REQUIRED) {
- mInstallViewModel.cleanupInstall();
- }
- setResult(Activity.RESULT_CANCELED, null, true);
- }
-
- @Override
- public void sendUnknownAppsIntent(String sourcePackageName) {
- Intent settingsIntent = new Intent();
- settingsIntent.setAction(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
- final Uri packageUri = Uri.parse("package:" + sourcePackageName);
- settingsIntent.setData(packageUri);
- settingsIntent.setFlags(FLAG_ACTIVITY_NO_HISTORY);
-
- try {
- registerAppOpChangeListener(new UnknownSourcesListener(sourcePackageName),
- sourcePackageName);
- startActivityForResult(settingsIntent, REQUEST_TRUST_EXTERNAL_SOURCE);
- } catch (ActivityNotFoundException exc) {
- Log.e(TAG, "Settings activity not found for action: "
- + Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
- }
- }
-
- @Override
- public void openInstalledApp(Intent intent) {
- setResult(RESULT_OK, intent, true);
- if (intent != null && intent.hasCategory(CATEGORY_LAUNCHER)) {
- startActivity(intent);
- }
- }
-
- private void registerAppOpChangeListener(UnknownSourcesListener listener, String packageName) {
- mAppOpsManager.startWatchingMode(
- AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES, packageName,
- listener);
- mActiveUnknownSourcesListeners.add(listener);
- }
-
- private void unregisterAppOpChangeListener(UnknownSourcesListener listener) {
- mActiveUnknownSourcesListeners.remove(listener);
- mAppOpsManager.stopWatchingMode(listener);
- }
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
- if (requestCode == REQUEST_TRUST_EXTERNAL_SOURCE) {
- mInstallViewModel.reattemptInstall();
- } else {
- setResult(Activity.RESULT_CANCELED, null, true);
- }
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- while (!mActiveUnknownSourcesListeners.isEmpty()) {
- unregisterAppOpChangeListener(mActiveUnknownSourcesListeners.get(0));
- }
- }
-
- private class UnknownSourcesListener implements AppOpsManager.OnOpChangedListener {
-
- private final String mOriginatingPackage;
-
- public UnknownSourcesListener(String originatingPackage) {
- mOriginatingPackage = originatingPackage;
- }
-
- @Override
- public void onOpChanged(String op, String packageName) {
- if (!mOriginatingPackage.equals(packageName)) {
- return;
- }
- unregisterAppOpChangeListener(this);
- mActiveUnknownSourcesListeners.remove(this);
- if (isDestroyed()) {
- return;
- }
- new Handler(Looper.getMainLooper()).postDelayed(() -> {
- if (!isDestroyed()) {
- // Relaunch Pia to continue installation.
- startActivity(getIntent()
- .putExtra(EXTRA_STAGED_SESSION_ID, mInstallViewModel.getStagedSessionId()));
-
- // If the userId of the root of activity stack is different from current userId,
- // starting Pia again lead to duplicate instances of the app in the stack.
- // As such, finish the old instance. Old Pia is finished even if the userId of
- // the root is the same, since there is no way to determine the difference in
- // userIds.
- finish();
- }
- }, 500);
- }
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt
new file mode 100644
index 000000000000..2b610d7b06f5
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt
@@ -0,0 +1,348 @@
+/*
+ * 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.packageinstaller.v2.ui
+
+import android.app.Activity
+import android.app.AppOpsManager
+import android.content.ActivityNotFoundException
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import android.os.Process
+import android.os.UserManager
+import android.provider.Settings
+import android.util.Log
+import android.view.Window
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.FragmentActivity
+import androidx.fragment.app.FragmentManager
+import androidx.lifecycle.ViewModelProvider
+import com.android.packageinstaller.R
+import com.android.packageinstaller.v2.model.InstallRepository
+import com.android.packageinstaller.v2.model.InstallAborted
+import com.android.packageinstaller.v2.model.InstallFailed
+import com.android.packageinstaller.v2.model.InstallInstalling
+import com.android.packageinstaller.v2.model.InstallStage
+import com.android.packageinstaller.v2.model.InstallSuccess
+import com.android.packageinstaller.v2.model.InstallUserActionRequired
+import com.android.packageinstaller.v2.ui.fragments.AnonymousSourceFragment
+import com.android.packageinstaller.v2.ui.fragments.ExternalSourcesBlockedFragment
+import com.android.packageinstaller.v2.ui.fragments.InstallConfirmationFragment
+import com.android.packageinstaller.v2.ui.fragments.InstallFailedFragment
+import com.android.packageinstaller.v2.ui.fragments.InstallInstallingFragment
+import com.android.packageinstaller.v2.ui.fragments.InstallStagingFragment
+import com.android.packageinstaller.v2.ui.fragments.InstallSuccessFragment
+import com.android.packageinstaller.v2.ui.fragments.SimpleErrorFragment
+import com.android.packageinstaller.v2.viewmodel.InstallViewModel
+import com.android.packageinstaller.v2.viewmodel.InstallViewModelFactory
+
+class InstallLaunch : FragmentActivity(), InstallActionListener {
+
+ companion object {
+ @JvmField val EXTRA_CALLING_PKG_UID =
+ InstallLaunch::class.java.packageName + ".callingPkgUid"
+ @JvmField val EXTRA_CALLING_PKG_NAME =
+ InstallLaunch::class.java.packageName + ".callingPkgName"
+ private val LOG_TAG = InstallLaunch::class.java.simpleName
+ private const val TAG_DIALOG = "dialog"
+ }
+
+ private val localLOGV = false
+
+ /**
+ * A collection of unknown sources listeners that are actively listening for app ops mode
+ * changes
+ */
+ private val activeUnknownSourcesListeners: MutableList<UnknownSourcesListener> = ArrayList(1)
+ private var installViewModel: InstallViewModel? = null
+ private var installRepository: InstallRepository? = null
+ private var fragmentManager: FragmentManager? = null
+ private var appOpsManager: AppOpsManager? = null
+ private lateinit var unknownAppsIntentLauncher: ActivityResultLauncher<Intent>
+
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ requestWindowFeature(Window.FEATURE_NO_TITLE)
+ fragmentManager = supportFragmentManager
+ appOpsManager = getSystemService(AppOpsManager::class.java)
+ installRepository = InstallRepository(applicationContext)
+ installViewModel = ViewModelProvider(
+ this, InstallViewModelFactory(this.application, installRepository!!)
+ )[InstallViewModel::class.java]
+
+ val intent = intent
+ val info = InstallRepository.CallerInfo(
+ intent.getStringExtra(EXTRA_CALLING_PKG_NAME),
+ intent.getIntExtra(EXTRA_CALLING_PKG_UID, Process.INVALID_UID)
+ )
+ installViewModel!!.preprocessIntent(intent, info)
+ installViewModel!!.currentInstallStage.observe(this) { installStage: InstallStage ->
+ onInstallStageChange(installStage)
+ }
+
+ // Used to launch intent for Settings, to manage "install unknown apps" permission
+ unknownAppsIntentLauncher =
+ registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
+ // Reattempt installation on coming back from Settings, after toggling
+ // "install unknown apps" permission
+ installViewModel!!.reattemptInstall()
+ }
+ }
+
+ /**
+ * Main controller of the UI. This method shows relevant dialogs based on the install stage
+ */
+ private fun onInstallStageChange(installStage: InstallStage) {
+ when (installStage.stageCode) {
+ InstallStage.STAGE_STAGING -> {
+ val stagingDialog = InstallStagingFragment()
+ showDialogInner(stagingDialog)
+ installViewModel!!.stagingProgress.observe(this) { progress: Int ->
+ stagingDialog.setProgress(progress)
+ }
+ }
+
+ InstallStage.STAGE_ABORTED -> {
+ val aborted = installStage as InstallAborted
+ when (aborted.abortReason) {
+ InstallAborted.ABORT_REASON_DONE, InstallAborted.ABORT_REASON_INTERNAL_ERROR ->
+ setResult(aborted.activityResultCode, aborted.resultIntent, true)
+
+ InstallAborted.ABORT_REASON_POLICY -> showPolicyRestrictionDialog(aborted)
+ else -> setResult(Activity.RESULT_CANCELED, null, true)
+ }
+ }
+
+ InstallStage.STAGE_USER_ACTION_REQUIRED -> {
+ val uar = installStage as InstallUserActionRequired
+ when (uar.actionReason) {
+ InstallUserActionRequired.USER_ACTION_REASON_INSTALL_CONFIRMATION -> {
+ val actionDialog = InstallConfirmationFragment(uar)
+ showDialogInner(actionDialog)
+ }
+
+ InstallUserActionRequired.USER_ACTION_REASON_UNKNOWN_SOURCE -> {
+ val externalSourceDialog = ExternalSourcesBlockedFragment(uar)
+ showDialogInner(externalSourceDialog)
+ }
+
+ InstallUserActionRequired.USER_ACTION_REASON_ANONYMOUS_SOURCE -> {
+ val anonymousSourceDialog = AnonymousSourceFragment()
+ showDialogInner(anonymousSourceDialog)
+ }
+ }
+ }
+
+ InstallStage.STAGE_INSTALLING -> {
+ val installing = installStage as InstallInstalling
+ val installingDialog = InstallInstallingFragment(installing)
+ showDialogInner(installingDialog)
+ }
+
+ InstallStage.STAGE_SUCCESS -> {
+ val success = installStage as InstallSuccess
+ if (success.shouldReturnResult) {
+ val successIntent = success.resultIntent
+ setResult(Activity.RESULT_OK, successIntent, true)
+ } else {
+ val successFragment = InstallSuccessFragment(success)
+ showDialogInner(successFragment)
+ }
+ }
+
+ InstallStage.STAGE_FAILED -> {
+ val failed = installStage as InstallFailed
+ val failedDialog = InstallFailedFragment(failed)
+ showDialogInner(failedDialog)
+ }
+
+ else -> {
+ Log.d(LOG_TAG, "Unimplemented stage: " + installStage.stageCode)
+ showDialogInner(null)
+ }
+ }
+ }
+
+ private fun showPolicyRestrictionDialog(aborted: InstallAborted) {
+ val restriction = aborted.message
+ val adminSupportIntent = aborted.resultIntent
+ var shouldFinish: Boolean = false
+
+ // If the given restriction is set by an admin, display information about the
+ // admin enforcing the restriction for the affected user. If not enforced by the admin,
+ // show the system dialog.
+ if (adminSupportIntent != null) {
+ if (localLOGV) {
+ Log.i(LOG_TAG, "Restriction set by admin, starting $adminSupportIntent")
+ }
+ startActivity(adminSupportIntent)
+ // Finish the package installer app since the next dialog will not be shown by this app
+ shouldFinish = true
+ } else {
+ if (localLOGV) {
+ Log.i(LOG_TAG, "Restriction set by system: $restriction")
+ }
+ val blockedByPolicyDialog = createDevicePolicyRestrictionDialog(restriction)
+ // Don't finish the package installer app since the next dialog
+ // will be shown by this app
+ shouldFinish = blockedByPolicyDialog != null
+ showDialogInner(blockedByPolicyDialog)
+ }
+ setResult(Activity.RESULT_CANCELED, null, shouldFinish)
+ }
+
+ /**
+ * Create a new dialog based on the install restriction enforced.
+ *
+ * @param restriction The restriction to create the dialog for
+ * @return The dialog
+ */
+ private fun createDevicePolicyRestrictionDialog(restriction: String?): DialogFragment? {
+ if (localLOGV) {
+ Log.i(LOG_TAG, "createDialog($restriction)")
+ }
+ return when (restriction) {
+ UserManager.DISALLOW_INSTALL_APPS ->
+ SimpleErrorFragment(R.string.install_apps_user_restriction_dlg_text)
+
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY ->
+ SimpleErrorFragment(R.string.unknown_apps_user_restriction_dlg_text)
+
+ else -> null
+ }
+ }
+
+ /**
+ * Replace any visible dialog by the dialog returned by InstallRepository
+ *
+ * @param newDialog The new dialog to display
+ */
+ private fun showDialogInner(newDialog: DialogFragment?) {
+ val currentDialog = fragmentManager!!.findFragmentByTag(TAG_DIALOG) as DialogFragment?
+ currentDialog?.dismissAllowingStateLoss()
+ newDialog?.show(fragmentManager!!, TAG_DIALOG)
+ }
+
+ fun setResult(resultCode: Int, data: Intent?, shouldFinish: Boolean) {
+ super.setResult(resultCode, data)
+ if (shouldFinish) {
+ finish()
+ }
+ }
+
+ override fun onPositiveResponse(reasonCode: Int) {
+ when (reasonCode) {
+ InstallUserActionRequired.USER_ACTION_REASON_ANONYMOUS_SOURCE ->
+ installViewModel!!.forcedSkipSourceCheck()
+
+ InstallUserActionRequired.USER_ACTION_REASON_INSTALL_CONFIRMATION ->
+ installViewModel!!.initiateInstall()
+ }
+ }
+
+ override fun onNegativeResponse(stageCode: Int) {
+ if (stageCode == InstallStage.STAGE_USER_ACTION_REQUIRED) {
+ installViewModel!!.cleanupInstall()
+ }
+ setResult(Activity.RESULT_CANCELED, null, true)
+ }
+
+ override fun sendUnknownAppsIntent(sourcePackageName: String) {
+ val settingsIntent = Intent()
+ settingsIntent.setAction(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES)
+ val packageUri = Uri.parse("package:$sourcePackageName")
+ settingsIntent.setData(packageUri)
+ settingsIntent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
+ try {
+ registerAppOpChangeListener(
+ UnknownSourcesListener(sourcePackageName), sourcePackageName
+ )
+ unknownAppsIntentLauncher.launch(settingsIntent)
+ } catch (exc: ActivityNotFoundException) {
+ Log.e(
+ LOG_TAG, "Settings activity not found for action: "
+ + Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES
+ )
+ }
+ }
+
+ override fun openInstalledApp(intent: Intent?) {
+ setResult(Activity.RESULT_OK, intent, true)
+ if (intent != null && intent.hasCategory(Intent.CATEGORY_LAUNCHER)) {
+ startActivity(intent)
+ }
+ }
+
+ private fun registerAppOpChangeListener(listener: UnknownSourcesListener, packageName: String) {
+ appOpsManager!!.startWatchingMode(
+ AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES,
+ packageName,
+ listener
+ )
+ activeUnknownSourcesListeners.add(listener)
+ }
+
+ private fun unregisterAppOpChangeListener(listener: UnknownSourcesListener) {
+ activeUnknownSourcesListeners.remove(listener)
+ appOpsManager!!.stopWatchingMode(listener)
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ while (activeUnknownSourcesListeners.isNotEmpty()) {
+ unregisterAppOpChangeListener(activeUnknownSourcesListeners[0])
+ }
+ }
+
+ private inner class UnknownSourcesListener(private val mOriginatingPackage: String) :
+ AppOpsManager.OnOpChangedListener {
+ override fun onOpChanged(op: String, packageName: String) {
+ if (mOriginatingPackage != packageName) {
+ return
+ }
+ unregisterAppOpChangeListener(this)
+ activeUnknownSourcesListeners.remove(this)
+ if (isDestroyed) {
+ return
+ }
+ Handler(Looper.getMainLooper()).postDelayed({
+ if (!isDestroyed) {
+ // Relaunch Pia to continue installation.
+ startActivity(
+ intent.putExtra(
+ InstallRepository.EXTRA_STAGED_SESSION_ID,
+ installViewModel!!.stagedSessionId
+ )
+ )
+
+ // If the userId of the root of activity stack is different from current userId,
+ // starting Pia again lead to duplicate instances of the app in the stack.
+ // As such, finish the old instance. Old Pia is finished even if the userId of
+ // the root is the same, since there is no way to determine the difference in
+ // userIds.
+ finish()
+ }
+ }, 500)
+ }
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.kt
index b8a93559d782..33f5db34f754 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallActionListener.kt
@@ -14,11 +14,9 @@
* limitations under the License.
*/
-package com.android.packageinstaller.v2.ui;
+package com.android.packageinstaller.v2.ui
-public interface UninstallActionListener {
-
- void onPositiveResponse(boolean keepData);
-
- void onNegativeResponse();
+interface UninstallActionListener {
+ fun onPositiveResponse(keepData: Boolean)
+ fun onNegativeResponse()
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.java
deleted file mode 100644
index 7638e917c7d5..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://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.packageinstaller.v2.ui;
-
-import static android.os.Process.INVALID_UID;
-import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
-
-import android.app.Activity;
-import android.app.NotificationManager;
-import android.content.Intent;
-import android.os.Bundle;
-import android.util.Log;
-import android.widget.Toast;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.DialogFragment;
-import androidx.fragment.app.FragmentActivity;
-import androidx.fragment.app.FragmentManager;
-import androidx.lifecycle.ViewModelProvider;
-import com.android.packageinstaller.v2.model.UninstallRepository;
-import com.android.packageinstaller.v2.model.UninstallRepository.CallerInfo;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallFailed;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallStage;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallSuccess;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallUninstalling;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallUserActionRequired;
-import com.android.packageinstaller.v2.ui.fragments.UninstallConfirmationFragment;
-import com.android.packageinstaller.v2.ui.fragments.UninstallErrorFragment;
-import com.android.packageinstaller.v2.ui.fragments.UninstallUninstallingFragment;
-import com.android.packageinstaller.v2.viewmodel.UninstallViewModel;
-import com.android.packageinstaller.v2.viewmodel.UninstallViewModelFactory;
-
-public class UninstallLaunch extends FragmentActivity implements UninstallActionListener {
-
- public static final String EXTRA_CALLING_PKG_UID =
- UninstallLaunch.class.getPackageName() + ".callingPkgUid";
- public static final String EXTRA_CALLING_ACTIVITY_NAME =
- UninstallLaunch.class.getPackageName() + ".callingActivityName";
- public static final String TAG = UninstallLaunch.class.getSimpleName();
- private static final String TAG_DIALOG = "dialog";
-
- private UninstallViewModel mUninstallViewModel;
- private UninstallRepository mUninstallRepository;
- private FragmentManager mFragmentManager;
- private NotificationManager mNotificationManager;
-
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
-
- // Never restore any state, esp. never create any fragments. The data in the fragment might
- // be stale, if e.g. the app was uninstalled while the activity was destroyed.
- super.onCreate(null);
-
- mFragmentManager = getSupportFragmentManager();
- mNotificationManager = getSystemService(NotificationManager.class);
-
- mUninstallRepository = new UninstallRepository(getApplicationContext());
- mUninstallViewModel = new ViewModelProvider(this,
- new UninstallViewModelFactory(this.getApplication(), mUninstallRepository)).get(
- UninstallViewModel.class);
-
- Intent intent = getIntent();
- CallerInfo callerInfo = new CallerInfo(
- intent.getStringExtra(EXTRA_CALLING_ACTIVITY_NAME),
- intent.getIntExtra(EXTRA_CALLING_PKG_UID, INVALID_UID));
- mUninstallViewModel.preprocessIntent(intent, callerInfo);
-
- mUninstallViewModel.getCurrentUninstallStage().observe(this,
- this::onUninstallStageChange);
- }
-
- /**
- * Main controller of the UI. This method shows relevant dialogs / fragments based on the
- * uninstall stage
- */
- private void onUninstallStageChange(UninstallStage uninstallStage) {
- if (uninstallStage.getStageCode() == UninstallStage.STAGE_ABORTED) {
- UninstallAborted aborted = (UninstallAborted) uninstallStage;
- if (aborted.getAbortReason() == UninstallAborted.ABORT_REASON_APP_UNAVAILABLE ||
- aborted.getAbortReason() == UninstallAborted.ABORT_REASON_USER_NOT_ALLOWED) {
- UninstallErrorFragment errorDialog = new UninstallErrorFragment(aborted);
- showDialogInner(errorDialog);
- } else {
- setResult(aborted.getActivityResultCode(), null, true);
- }
- } else if (uninstallStage.getStageCode() == UninstallStage.STAGE_USER_ACTION_REQUIRED) {
- UninstallUserActionRequired uar = (UninstallUserActionRequired) uninstallStage;
- UninstallConfirmationFragment confirmationDialog = new UninstallConfirmationFragment(
- uar);
- showDialogInner(confirmationDialog);
- } else if (uninstallStage.getStageCode() == UninstallStage.STAGE_UNINSTALLING) {
- // TODO: This shows a fragment whether or not user requests a result or not.
- // Originally, if the user does not request a result, we used to show a notification.
- // And a fragment if the user requests a result back. Should we consolidate and
- // show a fragment always?
- UninstallUninstalling uninstalling = (UninstallUninstalling) uninstallStage;
- UninstallUninstallingFragment uninstallingDialog = new UninstallUninstallingFragment(
- uninstalling);
- showDialogInner(uninstallingDialog);
- } else if (uninstallStage.getStageCode() == UninstallStage.STAGE_FAILED) {
- UninstallFailed failed = (UninstallFailed) uninstallStage;
- if (!failed.returnResult()) {
- mNotificationManager.notify(failed.getUninstallId(),
- failed.getUninstallNotification());
- }
- setResult(failed.getActivityResultCode(), failed.getResultIntent(), true);
- } else if (uninstallStage.getStageCode() == UninstallStage.STAGE_SUCCESS) {
- UninstallSuccess success = (UninstallSuccess) uninstallStage;
- if (success.getMessage() != null) {
- Toast.makeText(this, success.getMessage(), Toast.LENGTH_LONG).show();
- }
- setResult(success.getActivityResultCode(), success.getResultIntent(), true);
- } else {
- Log.e(TAG, "Invalid stage: " + uninstallStage.getStageCode());
- showDialogInner(null);
- }
- }
-
- /**
- * Replace any visible dialog by the dialog returned by InstallRepository
- *
- * @param newDialog The new dialog to display
- */
- private void showDialogInner(DialogFragment newDialog) {
- DialogFragment currentDialog = (DialogFragment) mFragmentManager.findFragmentByTag(
- TAG_DIALOG);
- if (currentDialog != null) {
- currentDialog.dismissAllowingStateLoss();
- }
- if (newDialog != null) {
- newDialog.show(mFragmentManager, TAG_DIALOG);
- }
- }
-
- public void setResult(int resultCode, Intent data, boolean shouldFinish) {
- super.setResult(resultCode, data);
- if (shouldFinish) {
- finish();
- }
- }
-
- @Override
- public void onPositiveResponse(boolean keepData) {
- mUninstallViewModel.initiateUninstall(keepData);
- }
-
- @Override
- public void onNegativeResponse() {
- mUninstallViewModel.cancelInstall();
- setResult(Activity.RESULT_FIRST_USER, null, true);
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.kt
new file mode 100644
index 000000000000..0050c7ebe93a
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/UninstallLaunch.kt
@@ -0,0 +1,169 @@
+/*
+ * 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
+ *
+ * https://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.packageinstaller.v2.ui
+
+import android.app.Activity
+import android.app.NotificationManager
+import android.content.Intent
+import android.os.Bundle
+import android.os.Process
+import android.util.Log
+import android.view.WindowManager
+import android.widget.Toast
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.FragmentActivity
+import androidx.fragment.app.FragmentManager
+import androidx.lifecycle.ViewModelProvider
+import com.android.packageinstaller.v2.model.UninstallAborted
+import com.android.packageinstaller.v2.model.UninstallFailed
+import com.android.packageinstaller.v2.model.UninstallRepository
+import com.android.packageinstaller.v2.model.UninstallStage
+import com.android.packageinstaller.v2.model.UninstallSuccess
+import com.android.packageinstaller.v2.model.UninstallUninstalling
+import com.android.packageinstaller.v2.model.UninstallUserActionRequired
+import com.android.packageinstaller.v2.ui.fragments.UninstallConfirmationFragment
+import com.android.packageinstaller.v2.ui.fragments.UninstallErrorFragment
+import com.android.packageinstaller.v2.ui.fragments.UninstallUninstallingFragment
+import com.android.packageinstaller.v2.viewmodel.UninstallViewModel
+import com.android.packageinstaller.v2.viewmodel.UninstallViewModelFactory
+
+class UninstallLaunch : FragmentActivity(), UninstallActionListener {
+
+ companion object {
+ @JvmField val EXTRA_CALLING_PKG_UID =
+ UninstallLaunch::class.java.packageName + ".callingPkgUid"
+ @JvmField val EXTRA_CALLING_ACTIVITY_NAME =
+ UninstallLaunch::class.java.packageName + ".callingActivityName"
+ val LOG_TAG = UninstallLaunch::class.java.simpleName
+ private const val TAG_DIALOG = "dialog"
+ }
+
+ private var uninstallViewModel: UninstallViewModel? = null
+ private var uninstallRepository: UninstallRepository? = null
+ private var fragmentManager: FragmentManager? = null
+ private var notificationManager: NotificationManager? = null
+ override fun onCreate(savedInstanceState: Bundle?) {
+ window.addSystemFlags(WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS)
+
+ // Never restore any state, esp. never create any fragments. The data in the fragment might
+ // be stale, if e.g. the app was uninstalled while the activity was destroyed.
+ super.onCreate(null)
+ fragmentManager = supportFragmentManager
+ notificationManager = getSystemService(NotificationManager::class.java)
+
+ uninstallRepository = UninstallRepository(applicationContext)
+ uninstallViewModel = ViewModelProvider(
+ this, UninstallViewModelFactory(this.application, uninstallRepository!!)
+ ).get(UninstallViewModel::class.java)
+
+ val intent = intent
+ val callerInfo = UninstallRepository.CallerInfo(
+ intent.getStringExtra(EXTRA_CALLING_ACTIVITY_NAME),
+ intent.getIntExtra(EXTRA_CALLING_PKG_UID, Process.INVALID_UID)
+ )
+ uninstallViewModel!!.preprocessIntent(intent, callerInfo)
+ uninstallViewModel!!.currentUninstallStage.observe(this) { uninstallStage: UninstallStage ->
+ onUninstallStageChange(uninstallStage)
+ }
+ }
+
+ /**
+ * Main controller of the UI. This method shows relevant dialogs / fragments based on the
+ * uninstall stage
+ */
+ private fun onUninstallStageChange(uninstallStage: UninstallStage) {
+ when (uninstallStage.stageCode) {
+ UninstallStage.STAGE_ABORTED -> {
+ val aborted = uninstallStage as UninstallAborted
+ if (aborted.abortReason == UninstallAborted.ABORT_REASON_APP_UNAVAILABLE ||
+ aborted.abortReason == UninstallAborted.ABORT_REASON_USER_NOT_ALLOWED
+ ) {
+ val errorDialog = UninstallErrorFragment(aborted)
+ showDialogInner(errorDialog)
+ } else {
+ setResult(aborted.activityResultCode, null, true)
+ }
+ }
+
+ UninstallStage.STAGE_USER_ACTION_REQUIRED -> {
+ val uar = uninstallStage as UninstallUserActionRequired
+ val confirmationDialog = UninstallConfirmationFragment(uar)
+ showDialogInner(confirmationDialog)
+ }
+
+ UninstallStage.STAGE_UNINSTALLING -> {
+ // TODO: This shows a fragment whether or not user requests a result or not.
+ // Originally, if the user does not request a result, we used to show a notification.
+ // And a fragment if the user requests a result back. Should we consolidate and
+ // show a fragment always?
+ val uninstalling = uninstallStage as UninstallUninstalling
+ val uninstallingDialog = UninstallUninstallingFragment(uninstalling)
+ showDialogInner(uninstallingDialog)
+ }
+
+ UninstallStage.STAGE_FAILED -> {
+ val failed = uninstallStage as UninstallFailed
+ if (!failed.returnResult) {
+ notificationManager!!.notify(
+ failed.uninstallNotificationId!!, failed.uninstallNotification
+ )
+ }
+ setResult(failed.activityResultCode, failed.resultIntent, true)
+ }
+
+ UninstallStage.STAGE_SUCCESS -> {
+ val success = uninstallStage as UninstallSuccess
+ if (success.message != null) {
+ Toast.makeText(this, success.message, Toast.LENGTH_LONG).show()
+ }
+ setResult(success.activityResultCode, success.resultIntent, true)
+ }
+
+ else -> {
+ Log.e(LOG_TAG, "Invalid stage: " + uninstallStage.stageCode)
+ showDialogInner(null)
+ }
+ }
+ }
+
+ /**
+ * Replace any visible dialog by the dialog returned by InstallRepository
+ *
+ * @param newDialog The new dialog to display
+ */
+ private fun showDialogInner(newDialog: DialogFragment?) {
+ val currentDialog = fragmentManager!!.findFragmentByTag(TAG_DIALOG) as DialogFragment?
+ currentDialog?.dismissAllowingStateLoss()
+ newDialog?.show(fragmentManager!!, TAG_DIALOG)
+ }
+
+ fun setResult(resultCode: Int, data: Intent?, shouldFinish: Boolean) {
+ super.setResult(resultCode, data)
+ if (shouldFinish) {
+ finish()
+ }
+ }
+
+ override fun onPositiveResponse(keepData: Boolean) {
+ uninstallViewModel!!.initiateUninstall(keepData)
+ }
+
+ override fun onNegativeResponse() {
+ uninstallViewModel!!.cancelInstall()
+ setResult(Activity.RESULT_FIRST_USER, null, true)
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/AnonymousSourceFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/AnonymousSourceFragment.java
index 6d6fcc94faf7..679f696ff59f 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/AnonymousSourceFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/AnonymousSourceFragment.java
@@ -24,8 +24,8 @@ import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.installstagedata.InstallStage;
-import com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired;
+import com.android.packageinstaller.v2.model.InstallStage;
+import com.android.packageinstaller.v2.model.InstallUserActionRequired;
import com.android.packageinstaller.v2.ui.InstallActionListener;
/**
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java
index 4cdce52e96ba..49901de96bc4 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java
@@ -25,7 +25,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired;
+import com.android.packageinstaller.v2.model.InstallUserActionRequired;
import com.android.packageinstaller.v2.ui.InstallActionListener;
/**
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java
index 6398aef5d573..25363d0b5f7b 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java
@@ -28,7 +28,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.installstagedata.InstallUserActionRequired;
+import com.android.packageinstaller.v2.model.InstallUserActionRequired;
import com.android.packageinstaller.v2.ui.InstallActionListener;
/**
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallFailedFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallFailedFragment.java
index d45cd76b2f2a..4667a7a4e48a 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallFailedFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallFailedFragment.java
@@ -28,7 +28,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.installstagedata.InstallFailed;
+import com.android.packageinstaller.v2.model.InstallFailed;
import com.android.packageinstaller.v2.ui.InstallActionListener;
/**
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallInstallingFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallInstallingFragment.java
index 9f60f96bdfac..7327b5d5b9c2 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallInstallingFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallInstallingFragment.java
@@ -25,7 +25,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.installstagedata.InstallInstalling;
+import com.android.packageinstaller.v2.model.InstallInstalling;
/**
* Dialog to show when an install is in progress.
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallSuccessFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallSuccessFragment.java
index ab6a93222d48..b2a65faa0a91 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallSuccessFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallSuccessFragment.java
@@ -29,8 +29,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.installstagedata.InstallStage;
-import com.android.packageinstaller.v2.model.installstagedata.InstallSuccess;
+import com.android.packageinstaller.v2.model.InstallStage;
+import com.android.packageinstaller.v2.model.InstallSuccess;
import com.android.packageinstaller.v2.ui.InstallActionListener;
import java.util.List;
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/SimpleErrorFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/SimpleErrorFragment.java
index 47fd67f0cf6b..58b8b2def6d0 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/SimpleErrorFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/SimpleErrorFragment.java
@@ -24,7 +24,7 @@ import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.installstagedata.InstallStage;
+import com.android.packageinstaller.v2.model.InstallStage;
import com.android.packageinstaller.v2.ui.InstallActionListener;
public class SimpleErrorFragment extends DialogFragment {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallConfirmationFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallConfirmationFragment.java
index 1b0885ea684a..32ac4a61b73e 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallConfirmationFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallConfirmationFragment.java
@@ -30,7 +30,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallUserActionRequired;
+import com.android.packageinstaller.v2.model.UninstallUserActionRequired;
import com.android.packageinstaller.v2.ui.UninstallActionListener;
/**
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallErrorFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallErrorFragment.java
index 305daba14f26..eb7183df07b9 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallErrorFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallErrorFragment.java
@@ -25,7 +25,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallAborted;
+import com.android.packageinstaller.v2.model.UninstallAborted;
import com.android.packageinstaller.v2.ui.UninstallActionListener;
/**
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallUninstallingFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallUninstallingFragment.java
index 23cc421890ac..835efc607fcb 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallUninstallingFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/UninstallUninstallingFragment.java
@@ -22,7 +22,7 @@ import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import com.android.packageinstaller.R;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallUninstalling;
+import com.android.packageinstaller.v2.model.UninstallUninstalling;
/**
* Dialog to show that the app is uninstalling.
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.java
deleted file mode 100644
index 04a0622627b9..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.viewmodel;
-
-import android.app.Application;
-import android.content.Intent;
-import androidx.annotation.NonNull;
-import androidx.lifecycle.AndroidViewModel;
-import androidx.lifecycle.MediatorLiveData;
-import androidx.lifecycle.MutableLiveData;
-import com.android.packageinstaller.v2.model.InstallRepository;
-import com.android.packageinstaller.v2.model.InstallRepository.CallerInfo;
-import com.android.packageinstaller.v2.model.installstagedata.InstallStage;
-import com.android.packageinstaller.v2.model.installstagedata.InstallStaging;
-
-
-public class InstallViewModel extends AndroidViewModel {
-
- private static final String TAG = InstallViewModel.class.getSimpleName();
- private final InstallRepository mRepository;
- private final MediatorLiveData<InstallStage> mCurrentInstallStage = new MediatorLiveData<>(
- new InstallStaging());
-
- public InstallViewModel(@NonNull Application application, InstallRepository repository) {
- super(application);
- mRepository = repository;
- }
-
- public MutableLiveData<InstallStage> getCurrentInstallStage() {
- return mCurrentInstallStage;
- }
-
- public void preprocessIntent(Intent intent, CallerInfo callerInfo) {
- InstallStage stage = mRepository.performPreInstallChecks(intent, callerInfo);
- if (stage.getStageCode() == InstallStage.STAGE_ABORTED) {
- mCurrentInstallStage.setValue(stage);
- } else {
- // Since staging is an async operation, we will get the staging result later in time.
- // Result of the file staging will be set in InstallRepository#mStagingResult.
- // As such, mCurrentInstallStage will need to add another MutableLiveData
- // as a data source
- mRepository.stageForInstall();
- mCurrentInstallStage.addSource(mRepository.getStagingResult(), installStage -> {
- if (installStage.getStageCode() != InstallStage.STAGE_READY) {
- mCurrentInstallStage.setValue(installStage);
- } else {
- checkIfAllowedAndInitiateInstall();
- }
- });
- }
- }
-
- public MutableLiveData<Integer> getStagingProgress() {
- return mRepository.getStagingProgress();
- }
-
- private void checkIfAllowedAndInitiateInstall() {
- InstallStage stage = mRepository.requestUserConfirmation();
- mCurrentInstallStage.setValue(stage);
- }
-
- public void forcedSkipSourceCheck() {
- InstallStage stage = mRepository.forcedSkipSourceCheck();
- mCurrentInstallStage.setValue(stage);
- }
-
- public void cleanupInstall() {
- mRepository.cleanupInstall();
- }
-
- public void reattemptInstall() {
- InstallStage stage = mRepository.reattemptInstall();
- mCurrentInstallStage.setValue(stage);
- }
-
- public void initiateInstall() {
- // Since installing is an async operation, we will get the install result later in time.
- // Result of the installation will be set in InstallRepository#mInstallResult.
- // As such, mCurrentInstallStage will need to add another MutableLiveData as a data source
- mRepository.initiateInstall();
- mCurrentInstallStage.addSource(mRepository.getInstallResult(), installStage -> {
- if (installStage != null) {
- mCurrentInstallStage.setValue(installStage);
- }
- });
- }
-
- public int getStagedSessionId() {
- return mRepository.getStagedSessionId();
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.kt
new file mode 100644
index 000000000000..072fb2d34928
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.kt
@@ -0,0 +1,96 @@
+/*
+ * 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.packageinstaller.v2.viewmodel
+
+import android.app.Application
+import android.content.Intent
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MediatorLiveData
+import androidx.lifecycle.MutableLiveData
+import com.android.packageinstaller.v2.model.InstallRepository
+import com.android.packageinstaller.v2.model.InstallStage
+import com.android.packageinstaller.v2.model.InstallStaging
+
+class InstallViewModel(application: Application, val repository: InstallRepository) :
+ AndroidViewModel(application) {
+
+ companion object {
+ private val LOG_TAG = InstallViewModel::class.java.simpleName
+ }
+
+ private val _currentInstallStage = MediatorLiveData<InstallStage>(InstallStaging())
+ val currentInstallStage: MutableLiveData<InstallStage>
+ get() = _currentInstallStage
+
+ fun preprocessIntent(intent: Intent, callerInfo: InstallRepository.CallerInfo) {
+ val stage = repository.performPreInstallChecks(intent, callerInfo)
+ if (stage.stageCode == InstallStage.STAGE_ABORTED) {
+ _currentInstallStage.value = stage
+ } else {
+ // Since staging is an async operation, we will get the staging result later in time.
+ // Result of the file staging will be set in InstallRepository#mStagingResult.
+ // As such, mCurrentInstallStage will need to add another MutableLiveData
+ // as a data source
+ repository.stageForInstall()
+ _currentInstallStage.addSource(repository.stagingResult) { installStage: InstallStage ->
+ if (installStage.stageCode != InstallStage.STAGE_READY) {
+ _currentInstallStage.value = installStage
+ } else {
+ checkIfAllowedAndInitiateInstall()
+ }
+ }
+ }
+ }
+
+ val stagingProgress: LiveData<Int>
+ get() = repository.stagingProgress
+
+ private fun checkIfAllowedAndInitiateInstall() {
+ val stage = repository.requestUserConfirmation()
+ _currentInstallStage.value = stage
+ }
+
+ fun forcedSkipSourceCheck() {
+ val stage = repository.forcedSkipSourceCheck()
+ _currentInstallStage.value = stage
+ }
+
+ fun cleanupInstall() {
+ repository.cleanupInstall()
+ }
+
+ fun reattemptInstall() {
+ val stage = repository.reattemptInstall()
+ _currentInstallStage.value = stage
+ }
+
+ fun initiateInstall() {
+ // Since installing is an async operation, we will get the install result later in time.
+ // Result of the installation will be set in InstallRepository#mInstallResult.
+ // As such, mCurrentInstallStage will need to add another MutableLiveData as a data source
+ repository.initiateInstall()
+ _currentInstallStage.addSource(repository.installResult) { installStage: InstallStage? ->
+ if (installStage != null) {
+ _currentInstallStage.value = installStage
+ }
+ }
+ }
+
+ val stagedSessionId: Int
+ get() = repository.stagedSessionId
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModelFactory.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModelFactory.java
deleted file mode 100644
index ef459e64d7d5..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModelFactory.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.viewmodel;
-
-import android.app.Application;
-import androidx.annotation.NonNull;
-import androidx.lifecycle.ViewModel;
-import androidx.lifecycle.ViewModelProvider;
-import com.android.packageinstaller.v2.model.InstallRepository;
-
-public class InstallViewModelFactory extends ViewModelProvider.AndroidViewModelFactory {
-
- private final InstallRepository mRepository;
- private final Application mApplication;
-
- public InstallViewModelFactory(Application application, InstallRepository repository) {
- // Calling super class' ctor ensures that create method is called correctly and the right
- // ctor of InstallViewModel is used. If we fail to do that, the default ctor:
- // InstallViewModel(application) is used, and repository isn't initialized in the viewmodel
- super(application);
- mApplication = application;
- mRepository = repository;
- }
-
- @NonNull
- @Override
- @SuppressWarnings("unchecked")
- public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
- return (T) new InstallViewModel(mApplication, mRepository);
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModelFactory.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModelFactory.kt
new file mode 100644
index 000000000000..07b2f4fcf2a1
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModelFactory.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.packageinstaller.v2.viewmodel
+
+import android.app.Application
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import com.android.packageinstaller.v2.model.InstallRepository
+
+class InstallViewModelFactory(val application: Application, val repository: InstallRepository) :
+ ViewModelProvider.AndroidViewModelFactory(application) {
+
+ // Calling super class' ctor ensures that create method is called correctly and the right
+ // ctor of InstallViewModel is used. If we fail to do that, the default ctor:
+ // InstallViewModel(application) is used, and repository isn't initialized in the viewmodel
+ override fun <T : ViewModel> create(modelClass: Class<T>): T {
+ return InstallViewModel(application, repository) as T
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.java
deleted file mode 100644
index 3f7bce8f85d0..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://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.packageinstaller.v2.viewmodel;
-
-import android.app.Application;
-import android.content.Intent;
-import androidx.annotation.NonNull;
-import androidx.lifecycle.AndroidViewModel;
-import androidx.lifecycle.MediatorLiveData;
-import androidx.lifecycle.MutableLiveData;
-import com.android.packageinstaller.v2.model.UninstallRepository;
-import com.android.packageinstaller.v2.model.UninstallRepository.CallerInfo;
-import com.android.packageinstaller.v2.model.uninstallstagedata.UninstallStage;
-
-public class UninstallViewModel extends AndroidViewModel {
-
- private static final String TAG = UninstallViewModel.class.getSimpleName();
- private final UninstallRepository mRepository;
- private final MediatorLiveData<UninstallStage> mCurrentUninstallStage =
- new MediatorLiveData<>();
-
- public UninstallViewModel(@NonNull Application application, UninstallRepository repository) {
- super(application);
- mRepository = repository;
- }
-
- public MutableLiveData<UninstallStage> getCurrentUninstallStage() {
- return mCurrentUninstallStage;
- }
-
- public void preprocessIntent(Intent intent, CallerInfo callerInfo) {
- UninstallStage stage = mRepository.performPreUninstallChecks(intent, callerInfo);
- if (stage.getStageCode() != UninstallStage.STAGE_ABORTED) {
- stage = mRepository.generateUninstallDetails();
- }
- mCurrentUninstallStage.setValue(stage);
- }
-
- public void initiateUninstall(boolean keepData) {
- mRepository.initiateUninstall(keepData);
- // Since uninstall is an async operation, we will get the uninstall result later in time.
- // Result of the uninstall will be set in UninstallRepository#mUninstallResult.
- // As such, mCurrentUninstallStage will need to add another MutableLiveData
- // as a data source
- mCurrentUninstallStage.addSource(mRepository.getUninstallResult(), uninstallStage -> {
- if (uninstallStage != null) {
- mCurrentUninstallStage.setValue(uninstallStage);
- }
- });
- }
-
- public void cancelInstall() {
- mRepository.cancelInstall();
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.kt
new file mode 100644
index 000000000000..80886e92e33e
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModel.kt
@@ -0,0 +1,62 @@
+/*
+ * 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
+ *
+ * https://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.packageinstaller.v2.viewmodel
+
+import android.app.Application
+import android.content.Intent
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.MediatorLiveData
+import androidx.lifecycle.MutableLiveData
+import com.android.packageinstaller.v2.model.UninstallRepository
+import com.android.packageinstaller.v2.model.UninstallStage
+
+class UninstallViewModel(application: Application, val repository: UninstallRepository) :
+ AndroidViewModel(application) {
+
+ companion object {
+ private val LOG_TAG = UninstallViewModel::class.java.simpleName
+ }
+
+ private val _currentUninstallStage = MediatorLiveData<UninstallStage>()
+ val currentUninstallStage: MutableLiveData<UninstallStage>
+ get() = _currentUninstallStage
+
+ fun preprocessIntent(intent: Intent, callerInfo: UninstallRepository.CallerInfo) {
+ var stage = repository.performPreUninstallChecks(intent, callerInfo)
+ if (stage.stageCode != UninstallStage.STAGE_ABORTED) {
+ stage = repository.generateUninstallDetails()
+ }
+ _currentUninstallStage.value = stage
+ }
+
+ fun initiateUninstall(keepData: Boolean) {
+ repository.initiateUninstall(keepData)
+ // Since uninstall is an async operation, we will get the uninstall result later in time.
+ // Result of the uninstall will be set in UninstallRepository#mUninstallResult.
+ // As such, _currentUninstallStage will need to add another MutableLiveData
+ // as a data source
+ _currentUninstallStage.addSource(repository.uninstallResult) { uninstallStage: UninstallStage? ->
+ if (uninstallStage != null) {
+ _currentUninstallStage.value = uninstallStage
+ }
+ }
+ }
+
+ fun cancelInstall() {
+ repository.cancelInstall()
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModelFactory.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModelFactory.java
deleted file mode 100644
index cd9845e2cfad..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModelFactory.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.packageinstaller.v2.viewmodel;
-
-import android.app.Application;
-import androidx.annotation.NonNull;
-import androidx.lifecycle.ViewModel;
-import androidx.lifecycle.ViewModelProvider;
-import com.android.packageinstaller.v2.model.UninstallRepository;
-
-public class UninstallViewModelFactory extends ViewModelProvider.AndroidViewModelFactory {
-
- private final UninstallRepository mRepository;
- private final Application mApplication;
-
- public UninstallViewModelFactory(Application application, UninstallRepository repository) {
- // Calling super class' ctor ensures that create method is called correctly and the right
- // ctor of UninstallViewModel is used. If we fail to do that, the default ctor:
- // UninstallViewModel(application) is used, and repository isn't initialized in
- // the viewmodel
- super(application);
- mApplication = application;
- mRepository = repository;
- }
-
- @NonNull
- @Override
- @SuppressWarnings("unchecked")
- public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
- return (T) new UninstallViewModel(mApplication, mRepository);
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModelFactory.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModelFactory.kt
new file mode 100644
index 000000000000..0a316e72791b
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/UninstallViewModelFactory.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.packageinstaller.v2.viewmodel
+
+import android.app.Application
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import com.android.packageinstaller.v2.model.UninstallRepository
+
+class UninstallViewModelFactory(val application: Application, val repository: UninstallRepository) :
+ ViewModelProvider.AndroidViewModelFactory(application) {
+
+ // Calling super class' ctor ensures that create method is called correctly and the right
+ // ctor of UninstallViewModel is used. If we fail to do that, the default ctor:
+ // UninstallViewModel(application) is used, and repository isn't initialized in
+ // the viewmodel
+ override fun <T : ViewModel> create(modelClass: Class<T>): T {
+ return UninstallViewModel(application, repository) as T
+ }
+}
diff --git a/packages/SettingsLib/AdaptiveIcon/Android.bp b/packages/SettingsLib/AdaptiveIcon/Android.bp
index df72a921544d..044ba872f3e5 100644
--- a/packages/SettingsLib/AdaptiveIcon/Android.bp
+++ b/packages/SettingsLib/AdaptiveIcon/Android.bp
@@ -15,9 +15,12 @@ android_library {
resource_dirs: ["res"],
static_libs: [
- "androidx.annotation_annotation",
- "SettingsLibTile"
+ "androidx.annotation_annotation",
+ "SettingsLibTile",
],
min_sdk_version: "21",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 2501869341ad..ffe3d1d633d7 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -60,6 +60,9 @@ android_library {
"src/**/*.java",
"src/**/*.kt",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
// NOTE: Keep this module in sync with ./common.mk
diff --git a/packages/SettingsLib/EmergencyNumber/Android.bp b/packages/SettingsLib/EmergencyNumber/Android.bp
index 986baf70fa44..bd0dbdc6884d 100644
--- a/packages/SettingsLib/EmergencyNumber/Android.bp
+++ b/packages/SettingsLib/EmergencyNumber/Android.bp
@@ -17,4 +17,7 @@ android_library {
],
sdk_version: "system_current",
min_sdk_version: "21",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/packages/SettingsLib/MainSwitchPreference/Android.bp b/packages/SettingsLib/MainSwitchPreference/Android.bp
index 010a6ce9d4d9..d9f74dadf281 100644
--- a/packages/SettingsLib/MainSwitchPreference/Android.bp
+++ b/packages/SettingsLib/MainSwitchPreference/Android.bp
@@ -28,4 +28,7 @@ android_library {
"com.android.extservices",
"com.android.healthfitness",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/packages/SettingsLib/RestrictedLockUtils/Android.bp b/packages/SettingsLib/RestrictedLockUtils/Android.bp
index 028489d22bdb..3b04bd99e0f4 100644
--- a/packages/SettingsLib/RestrictedLockUtils/Android.bp
+++ b/packages/SettingsLib/RestrictedLockUtils/Android.bp
@@ -30,4 +30,7 @@ android_library {
"//apex_available:platform",
"com.android.permission",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/packages/SettingsLib/SchedulesProvider/Android.bp b/packages/SettingsLib/SchedulesProvider/Android.bp
index 2d93e4e3a925..22e4e94b80b1 100644
--- a/packages/SettingsLib/SchedulesProvider/Android.bp
+++ b/packages/SettingsLib/SchedulesProvider/Android.bp
@@ -14,9 +14,12 @@ android_library {
srcs: ["src/**/*.java"],
static_libs: [
- "androidx.annotation_annotation",
+ "androidx.annotation_annotation",
],
sdk_version: "system_current",
min_sdk_version: "21",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/packages/SettingsLib/SearchProvider/Android.bp b/packages/SettingsLib/SearchProvider/Android.bp
index c07a802f3ed5..c385d385dcc9 100644
--- a/packages/SettingsLib/SearchProvider/Android.bp
+++ b/packages/SettingsLib/SearchProvider/Android.bp
@@ -15,4 +15,7 @@ android_library {
sdk_version: "system_current",
min_sdk_version: "21",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/packages/SettingsLib/Tile/Android.bp b/packages/SettingsLib/Tile/Android.bp
index eb9e329b6a59..19c59dd221c2 100644
--- a/packages/SettingsLib/Tile/Android.bp
+++ b/packages/SettingsLib/Tile/Android.bp
@@ -14,8 +14,11 @@ android_library {
srcs: ["src/**/*.java"],
static_libs: [
- "androidx.annotation_annotation",
+ "androidx.annotation_annotation",
],
min_sdk_version: "21",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/packages/SettingsLib/Utils/Android.bp b/packages/SettingsLib/Utils/Android.bp
index c7ad24c6148c..d5a56c86431f 100644
--- a/packages/SettingsLib/Utils/Android.bp
+++ b/packages/SettingsLib/Utils/Android.bp
@@ -31,4 +31,7 @@ android_library {
"com.android.healthfitness",
"com.android.permission",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/packages/SettingsLib/search/Android.bp b/packages/SettingsLib/search/Android.bp
index 202e7fe219de..3b14712cc87e 100644
--- a/packages/SettingsLib/search/Android.bp
+++ b/packages/SettingsLib/search/Android.bp
@@ -12,6 +12,9 @@ java_library {
visibility: ["//visibility:private"],
srcs: ["interface-src/**/*.java"],
host_supported: true,
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
android_library {
@@ -24,6 +27,9 @@ android_library {
sdk_version: "system_current",
min_sdk_version: "21",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
java_plugin {
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index e5dbe5f14cf4..d6e8d26af1a0 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -112,5 +112,7 @@ public class GlobalSettings {
Settings.Global.Wearable.SCREENSHOT_ENABLED,
Settings.Global.Wearable.SCREEN_UNLOCK_SOUND_ENABLED,
Settings.Global.Wearable.CHARGING_SOUNDS_ENABLED,
+ Settings.Global.Wearable.WRIST_DETECTION_AUTO_LOCKING_ENABLED,
+ Settings.Global.FORCE_ENABLE_PSS_PROFILING,
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index d9fe7335dbcb..f8bdcf65c45d 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -209,6 +209,7 @@ public class GlobalSettingsValidators {
VALIDATORS.put(Global.ADAPTIVE_BATTERY_MANAGEMENT_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.POWER_BUTTON_LONG_PRESS_DURATION_MS, NONE_NEGATIVE_LONG_VALIDATOR);
VALIDATORS.put(Global.STYLUS_EVER_USED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Global.MUTE_ALARM_STREAM_WITH_RINGER_MODE, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.HAS_PAY_TOKENS, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.GMS_CHECKIN_TIMEOUT_MIN, ANY_INTEGER_VALIDATOR);
@@ -446,5 +447,7 @@ public class GlobalSettingsValidators {
VALIDATORS.put(Global.Wearable.WEAR_LAUNCHER_UI_MODE, ANY_INTEGER_VALIDATOR);
VALIDATORS.put(Global.Wearable.WEAR_POWER_ANOMALY_SERVICE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.CONNECTIVITY_KEEP_DATA_ON, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Global.Wearable.WRIST_DETECTION_AUTO_LOCKING_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Global.FORCE_ENABLE_PSS_PROFILING, BOOLEAN_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 85e87691ac85..6ad10cc8cc5b 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -350,6 +350,7 @@ public class SettingsBackupTest {
Settings.Global.DSRM_DURATION_MILLIS,
Settings.Global.DSRM_ENABLED_ACTIONS,
Settings.Global.MODE_RINGER,
+ Settings.Global.MUTE_ALARM_STREAM_WITH_RINGER_MODE,
Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION,
Settings.Global.MULTI_SIM_SMS_PROMPT,
Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION,
diff --git a/packages/SystemUI/aconfig/Android.bp b/packages/SystemUI/aconfig/Android.bp
index 50ed7ab7c85a..7f16ca52d949 100644
--- a/packages/SystemUI/aconfig/Android.bp
+++ b/packages/SystemUI/aconfig/Android.bp
@@ -23,7 +23,8 @@ package {
default_visibility: [
"//visibility:override",
"//frameworks/base/packages/SystemUI:__subpackages__",
- "//platform_testing:__subpackages__"
+ "//frameworks/libs/systemui/tracinglib:__subpackages__",
+ "//platform_testing:__subpackages__",
],
}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 98a2d9ff2d19..41d12dca869e 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -205,6 +205,13 @@ flag {
}
flag {
+ name: "pss_task_switcher"
+ namespace: "systemui"
+ description: "Enable the task switcher feature for partial screen sharing"
+ bug: "317208379"
+}
+
+flag {
name: "rest_to_unlock"
namespace: "systemui"
description: "Require prolonged touch for fingerprint authentication"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 2a9cf0fdc507..55fc3a2a81c1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -20,10 +20,12 @@ import android.os.Bundle
import android.util.SizeF
import android.widget.FrameLayout
import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@@ -31,11 +33,13 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
+import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Edit
@@ -98,7 +102,6 @@ fun CommunalHub(
modifier = modifier.fillMaxSize().background(Color.White),
) {
CommunalHubLazyGrid(
- modifier = Modifier.align(Alignment.CenterStart),
communalContent = communalContent,
viewModel = viewModel,
contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize),
@@ -138,21 +141,21 @@ fun CommunalHub(
@OptIn(ExperimentalFoundationApi::class)
@Composable
-private fun CommunalHubLazyGrid(
+private fun BoxScope.CommunalHubLazyGrid(
communalContent: List<CommunalContentModel>,
viewModel: BaseCommunalViewModel,
- modifier: Modifier = Modifier,
contentPadding: PaddingValues,
setGridCoordinates: (coordinates: LayoutCoordinates) -> Unit,
updateDragPositionForRemove: (offset: Offset) -> Boolean,
) {
- var gridModifier = modifier
+ var gridModifier = Modifier.align(Alignment.CenterStart)
val gridState = rememberLazyGridState()
var list = communalContent
var dragDropState: GridDragDropState? = null
if (viewModel.isEditMode && viewModel is CommunalEditModeViewModel) {
val contentListState = rememberContentListState(communalContent, viewModel)
list = contentListState.list
+ // for drag & drop operations within the communal hub grid
dragDropState =
rememberGridDragDropState(
gridState = gridState,
@@ -164,9 +167,22 @@ private fun CommunalHubLazyGrid(
.fillMaxSize()
.dragContainer(dragDropState, beforeContentPadding(contentPadding))
.onGloballyPositioned { setGridCoordinates(it) }
+ // for widgets dropped from other activities
+ val dragAndDropTargetState =
+ rememberDragAndDropTargetState(
+ gridState = gridState,
+ contentListState = contentListState,
+ updateDragPositionForRemove = updateDragPositionForRemove
+ )
+
+ // A full size box in background that listens to widget drops from the picker.
+ // Since the grid has its own listener for in-grid drag events, we use a separate element
+ // for android drag events.
+ Box(Modifier.fillMaxSize().dragAndDropTarget(dragAndDropTargetState)) {}
} else {
gridModifier = gridModifier.height(Dimensions.GridHeight)
}
+
LazyHorizontalGrid(
modifier = gridModifier,
state = gridState,
@@ -309,12 +325,24 @@ private fun CommunalContent(
) {
when (model) {
is CommunalContentModel.Widget -> WidgetContent(model, size, elevation, modifier)
+ is CommunalContentModel.WidgetPlaceholder -> WidgetPlaceholderContent(size)
is CommunalContentModel.Smartspace -> SmartspaceContent(model, modifier)
is CommunalContentModel.Tutorial -> TutorialContent(modifier)
is CommunalContentModel.Umo -> Umo(viewModel, modifier)
}
}
+/** Presents a placeholder card for the new widget being dragged and dropping into the grid. */
+@Composable
+fun WidgetPlaceholderContent(size: SizeF) {
+ Card(
+ modifier = Modifier.size(Dp(size.width), Dp(size.height)),
+ colors = CardDefaults.cardColors(containerColor = Color.Transparent),
+ border = BorderStroke(3.dp, LocalAndroidColorScheme.current.tertiaryFixed),
+ shape = RoundedCornerShape(16.dp)
+ ) {}
+}
+
@Composable
private fun WidgetContent(
model: CommunalContentModel.Widget,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
index 89c57658b474..979991d7dc2a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
@@ -16,11 +16,10 @@
package com.android.systemui.communal.ui.compose
+import android.content.ComponentName
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
+import androidx.compose.runtime.toMutableStateList
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
@@ -32,6 +31,7 @@ fun rememberContentListState(
return remember(communalContent) {
ContentListState(
communalContent,
+ viewModel::onAddWidget,
viewModel::onDeleteWidget,
viewModel::onReorderWidgets,
)
@@ -46,30 +46,57 @@ fun rememberContentListState(
class ContentListState
internal constructor(
communalContent: List<CommunalContentModel>,
+ private val onAddWidget: (componentName: ComponentName, priority: Int) -> Unit,
private val onDeleteWidget: (id: Int) -> Unit,
- private val onReorderWidgets: (ids: List<Int>) -> Unit,
+ private val onReorderWidgets: (widgetIdToPriorityMap: Map<Int, Int>) -> Unit,
) {
- var list by mutableStateOf(communalContent)
+ var list = communalContent.toMutableStateList()
private set
/** Move item to a new position in the list. */
fun onMove(fromIndex: Int, toIndex: Int) {
- list = list.toMutableList().apply { add(toIndex, removeAt(fromIndex)) }
+ list.apply { add(toIndex, removeAt(fromIndex)) }
}
/** Remove widget from the list and the database. */
fun onRemove(indexToRemove: Int) {
if (list[indexToRemove] is CommunalContentModel.Widget) {
val widget = list[indexToRemove] as CommunalContentModel.Widget
- list = list.toMutableList().apply { removeAt(indexToRemove) }
+ list.apply { removeAt(indexToRemove) }
onDeleteWidget(widget.appWidgetId)
}
}
- /** Persist the new order with all the movements happened during dragging. */
- fun onSaveList() {
- val widgetIds: List<Int> =
- list.filterIsInstance<CommunalContentModel.Widget>().map { it.appWidgetId }
- onReorderWidgets(widgetIds)
+ /**
+ * Persists the new order with all the movements happened during drag operations & the new
+ * widget drop (if applicable).
+ *
+ * @param newItemComponentName name of the new widget that was dropped into the list; null if no
+ * new widget was added.
+ * @param newItemIndex index at which the a new widget was dropped into the list; null if no new
+ * widget was dropped.
+ */
+ fun onSaveList(newItemComponentName: ComponentName? = null, newItemIndex: Int? = null) {
+ // filters placeholder, but, maintains the indices of the widgets as if the placeholder was
+ // in the list. When persisted in DB, this leaves space for the new item (to be added) at
+ // the correct priority.
+ val widgetIdToPriorityMap: Map<Int, Int> =
+ list
+ .mapIndexedNotNull { index, item ->
+ if (item is CommunalContentModel.Widget) {
+ item.appWidgetId to list.size - index
+ } else {
+ null
+ }
+ }
+ .toMap()
+ // reorder and then add the new widget
+ onReorderWidgets(widgetIdToPriorityMap)
+ if (newItemComponentName != null && newItemIndex != null) {
+ onAddWidget(newItemComponentName, /*priority=*/ list.size - newItemIndex)
+ }
}
+
+ /** Returns true if the item at given index is editable. */
+ fun isItemEditable(index: Int) = list[index] is CommunalContentModel.Widget
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
new file mode 100644
index 000000000000..22aa8378cdd0
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
@@ -0,0 +1,274 @@
+/*
+ * 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.communal.ui.compose
+
+import android.content.ClipDescription
+import android.content.ComponentName
+import android.content.Intent
+import android.view.DragEvent
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.draganddrop.dragAndDropTarget
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.scrollBy
+import androidx.compose.foundation.lazy.grid.LazyGridItemInfo
+import androidx.compose.foundation.lazy.grid.LazyGridState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableFloatStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draganddrop.DragAndDropEvent
+import androidx.compose.ui.draganddrop.DragAndDropTarget
+import androidx.compose.ui.draganddrop.mimeTypes
+import androidx.compose.ui.draganddrop.toAndroidDragEvent
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.dp
+import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.ui.compose.extensions.plus
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.isActive
+import kotlinx.coroutines.launch
+
+/**
+ * Holds state associated with dragging and dropping items from other activities into the lazy grid.
+ *
+ * @see dragAndDropTarget
+ */
+@Composable
+internal fun rememberDragAndDropTargetState(
+ gridState: LazyGridState,
+ contentListState: ContentListState,
+ updateDragPositionForRemove: (offset: Offset) -> Boolean,
+): DragAndDropTargetState {
+ val scope = rememberCoroutineScope()
+ val autoScrollSpeed = remember { mutableFloatStateOf(0f) }
+ // Threshold of distance from edges that should start auto-scroll - chosen to be a narrow value
+ // that allows differentiating intention of scrolling from intention of dragging over the first
+ // visible item.
+ val autoScrollThreshold = with(LocalDensity.current) { 60.dp.toPx() }
+ val state =
+ remember(gridState, contentListState) {
+ DragAndDropTargetState(
+ state = gridState,
+ contentListState = contentListState,
+ scope = scope,
+ autoScrollSpeed = autoScrollSpeed,
+ autoScrollThreshold = autoScrollThreshold,
+ updateDragPositionForRemove = updateDragPositionForRemove,
+ )
+ }
+ LaunchedEffect(autoScrollSpeed.floatValue) {
+ if (autoScrollSpeed.floatValue != 0f) {
+ while (isActive) {
+ gridState.scrollBy(autoScrollSpeed.floatValue)
+ delay(10)
+ }
+ }
+ }
+ return state
+}
+
+/**
+ * Attaches a listener for drag and drop events from other activities.
+ *
+ * @see androidx.compose.foundation.draganddrop.dragAndDropTarget
+ * @see DragEvent
+ */
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+internal fun Modifier.dragAndDropTarget(
+ dragDropTargetState: DragAndDropTargetState,
+): Modifier {
+ val state by rememberUpdatedState(dragDropTargetState)
+
+ return this then
+ Modifier.dragAndDropTarget(
+ shouldStartDragAndDrop = accept@{ startEvent ->
+ startEvent.mimeTypes().any { it == ClipDescription.MIMETYPE_TEXT_INTENT }
+ },
+ target =
+ object : DragAndDropTarget {
+ override fun onStarted(event: DragAndDropEvent) {
+ state.onStarted()
+ }
+
+ override fun onMoved(event: DragAndDropEvent) {
+ state.onMoved(event)
+ }
+
+ override fun onDrop(event: DragAndDropEvent): Boolean {
+ return state.onDrop(event)
+ }
+
+ override fun onEnded(event: DragAndDropEvent) {
+ state.onEnded()
+ }
+ }
+ )
+}
+
+/**
+ * Handles dropping of an item coming from a different activity (e.g. widget picker) in to the grid
+ * corresponding to the provided [LazyGridState].
+ *
+ * Adds a placeholder container to highlight the anticipated location the widget will be dropped to.
+ * When the item is held over an empty area, the placeholder appears at the end of the grid if one
+ * didn't exist already. As user moves the item over an existing item, the placeholder appears in
+ * place of that existing item. And then, the existing item is pushed over as part of re-ordering.
+ *
+ * Once item is dropped, new ordering along with the dropped item is persisted. See
+ * [ContentListState.onSaveList].
+ *
+ * Difference between this and [GridDragDropState] is that, this is used for listening to drops from
+ * other activities. [GridDragDropState] on the other hand, handles dragging of existing items in
+ * the communal hub grid.
+ */
+internal class DragAndDropTargetState(
+ private val state: LazyGridState,
+ private val contentListState: ContentListState,
+ private val scope: CoroutineScope,
+ private val autoScrollSpeed: MutableState<Float>,
+ private val autoScrollThreshold: Float,
+ private val updateDragPositionForRemove: (offset: Offset) -> Boolean,
+) {
+ /**
+ * The placeholder item that is treated as if it is being dragged across the grid. It is added
+ * to grid once drag and drop event is started and removed when event ends.
+ */
+ private var placeHolder = CommunalContentModel.WidgetPlaceholder()
+
+ private var placeHolderIndex: Int? = null
+ private var isOnRemoveButton = false
+
+ fun onStarted() {
+ // assume item will be added to the end.
+ contentListState.list.add(placeHolder)
+ placeHolderIndex = contentListState.list.size - 1
+ }
+
+ fun onMoved(event: DragAndDropEvent) {
+ val dragEvent = event.toAndroidDragEvent()
+ isOnRemoveButton = updateDragPositionForRemove(Offset(dragEvent.x, dragEvent.y))
+ if (!isOnRemoveButton) {
+ findTargetItem(dragEvent)?.apply {
+ var scrollIndex: Int? = null
+ var scrollOffset: Int? = null
+ if (placeHolderIndex == state.firstVisibleItemIndex) {
+ // Save info about the first item before the move, to neutralize the automatic
+ // keeping first item first.
+ scrollIndex = placeHolderIndex
+ scrollOffset = state.firstVisibleItemScrollOffset
+ }
+
+ autoScrollIfNearEdges(dragEvent)
+
+ if (contentListState.isItemEditable(this.index)) {
+ movePlaceholderTo(this.index)
+ placeHolderIndex = this.index
+ }
+
+ if (scrollIndex != null && scrollOffset != null) {
+ // this is needed to neutralize automatic keeping the first item first.
+ scope.launch { state.scrollToItem(scrollIndex, scrollOffset) }
+ }
+ }
+ }
+ }
+
+ fun onDrop(event: DragAndDropEvent): Boolean {
+ autoScrollSpeed.value = 0f
+ if (isOnRemoveButton) {
+ return false
+ }
+ return placeHolderIndex?.let { dropIndex ->
+ val componentName = event.maybeWidgetComponentName()
+ if (componentName != null) {
+ // Placeholder isn't removed yet to allow the setting the right priority for items
+ // before adding in the new item.
+ contentListState.onSaveList(
+ newItemComponentName = componentName,
+ newItemIndex = dropIndex
+ )
+ return@let true
+ }
+ return false
+ }
+ ?: false
+ }
+
+ fun onEnded() {
+ autoScrollSpeed.value = 0f
+ placeHolderIndex = null
+ contentListState.list.remove(placeHolder)
+ isOnRemoveButton = updateDragPositionForRemove(Offset.Zero)
+ }
+
+ private fun autoScrollIfNearEdges(dragEvent: DragEvent) {
+ val orientation = state.layoutInfo.orientation
+ val distanceFromStart =
+ if (orientation == Orientation.Horizontal) {
+ dragEvent.x
+ } else {
+ dragEvent.y
+ }
+ val distanceFromEnd =
+ if (orientation == Orientation.Horizontal) {
+ state.layoutInfo.viewportSize.width - dragEvent.x
+ } else {
+ state.layoutInfo.viewportSize.height - dragEvent.y
+ }
+ autoScrollSpeed.value =
+ when {
+ distanceFromEnd < autoScrollThreshold -> autoScrollThreshold - distanceFromEnd
+ distanceFromStart < autoScrollThreshold ->
+ -(autoScrollThreshold - distanceFromStart)
+ else -> 0f
+ }
+ }
+
+ private fun findTargetItem(dragEvent: DragEvent): LazyGridItemInfo? =
+ state.layoutInfo.visibleItemsInfo.firstOrNull { item ->
+ dragEvent.x.toInt() in item.offset.x..(item.offset + item.size).x &&
+ dragEvent.y.toInt() in item.offset.y..(item.offset + item.size).y
+ }
+
+ private fun movePlaceholderTo(index: Int) {
+ val currentIndex = contentListState.list.indexOf(placeHolder)
+ if (currentIndex != index) {
+ contentListState.onMove(currentIndex, index)
+ }
+ }
+
+ /**
+ * Parses and returns the component name of the widget that was dropped into the communal grid.
+ *
+ * Returns null if the drop event didn't include the widget information.
+ */
+ private fun DragAndDropEvent.maybeWidgetComponentName(): ComponentName? {
+ val clipData = this.toAndroidDragEvent().clipData.takeIf { it.itemCount != 0 }
+ return clipData
+ ?.getItemAt(0)
+ ?.intent
+ ?.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME, ComponentName::class.java)
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
index 5451d0550095..0d460aa8d464 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
@@ -32,15 +32,13 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.unit.IntOffset
-import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.toOffset
import androidx.compose.ui.unit.toSize
import androidx.compose.ui.zIndex
-import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.ui.compose.extensions.plus
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
@@ -112,7 +110,7 @@ internal constructor(
.firstOrNull { item ->
// grid item offset is based off grid content container so we need to deduct
// before content padding from the initial pointer position
- item.isEditable &&
+ contentListState.isItemEditable(item.index) &&
(offset.x - contentOffset.x).toInt() in item.offset.x..item.offsetEnd.x &&
(offset.y - contentOffset.y).toInt() in item.offset.y..item.offsetEnd.y
}
@@ -149,7 +147,7 @@ internal constructor(
val targetItem =
state.layoutInfo.visibleItemsInfo.find { item ->
- item.isEditable &&
+ contentListState.isItemEditable(item.index) &&
middleOffset.x.toInt() in item.offset.x..item.offsetEnd.x &&
middleOffset.y.toInt() in item.offset.y..item.offsetEnd.y &&
draggingItem.index != item.index
@@ -187,10 +185,6 @@ internal constructor(
private val LazyGridItemInfo.offsetEnd: IntOffset
get() = this.offset + this.size
- /** Whether the grid item can be dragged or be a drop target. Only widget card is editable. */
- private val LazyGridItemInfo.isEditable: Boolean
- get() = contentListState.list[this.index] is CommunalContentModel.Widget
-
/** Calculate the amount dragged out of bound on both sides. Returns 0f if not overscrolled */
private fun checkForOverscroll(startOffset: Offset, endOffset: Offset): Float {
return when {
@@ -210,14 +204,6 @@ internal constructor(
}
}
-private operator fun IntOffset.plus(size: IntSize): IntOffset {
- return IntOffset(x + size.width, y + size.height)
-}
-
-private operator fun Offset.plus(size: Size): Offset {
- return Offset(x + size.width, y + size.height)
-}
-
fun Modifier.dragContainer(
dragDropState: GridDragDropState,
beforeContentPadding: ContentPaddingInPx
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallStage.java b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/OffsetOperators.kt
index 87ca4ec37349..b86c07e2e311 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/uninstallstagedata/UninstallStage.java
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/OffsetOperators.kt
@@ -14,17 +14,19 @@
* limitations under the License.
*/
-package com.android.packageinstaller.v2.model.uninstallstagedata;
+package com.android.systemui.communal.ui.compose.extensions
-public abstract class UninstallStage {
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntSize
- public static final int STAGE_DEFAULT = -1;
- public static final int STAGE_ABORTED = 0;
- public static final int STAGE_READY = 1;
- public static final int STAGE_USER_ACTION_REQUIRED = 2;
- public static final int STAGE_UNINSTALLING = 3;
- public static final int STAGE_SUCCESS = 4;
- public static final int STAGE_FAILED = 5;
+/** Adds the given size to the x and y offsets in this [IntOffset] */
+operator fun IntOffset.plus(size: IntSize): IntOffset {
+ return IntOffset(x + size.width, y + size.height)
+}
- public abstract int getStageCode();
+/** Adds the given size to the x and y offsets in this [Offset]. */
+operator fun Offset.plus(size: Size): Offset {
+ return Offset(x + size.width, y + size.height)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt
new file mode 100644
index 000000000000..472484aa74d9
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalFoundationApi::class)
+
+package com.android.systemui.keyguard.ui.composable
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.combinedClickable
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.input.pointer.pointerInput
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
+
+/** Container for lockscreen content that handles long-press to bring up the settings menu. */
+@Composable
+fun LockscreenLongPress(
+ viewModel: KeyguardLongPressViewModel,
+ modifier: Modifier = Modifier,
+ content: @Composable BoxScope.(onSettingsMenuPlaces: (coordinates: Rect?) -> Unit) -> Unit,
+) {
+ val isEnabled: Boolean by viewModel.isLongPressHandlingEnabled.collectAsState(initial = false)
+ val (settingsMenuBounds, setSettingsMenuBounds) = remember { mutableStateOf<Rect?>(null) }
+ val interactionSource = remember { MutableInteractionSource() }
+
+ Box(
+ modifier =
+ modifier
+ .combinedClickable(
+ enabled = isEnabled,
+ onLongClick = viewModel::onLongPress,
+ onClick = {},
+ interactionSource = interactionSource,
+ // Passing null for the indication removes the ripple effect.
+ indication = null,
+ )
+ .pointerInput(settingsMenuBounds) {
+ awaitEachGesture {
+ val pointerInputChange = awaitFirstDown()
+ if (settingsMenuBounds?.contains(pointerInputChange.position) == false) {
+ viewModel.onTouchedOutside()
+ }
+ }
+ },
+ ) {
+ content(setSettingsMenuBounds)
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index 93f31ec8f7ed..67a68200f269 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -14,36 +14,15 @@
* limitations under the License.
*/
-@file:OptIn(ExperimentalFoundationApi::class)
-
package com.android.systemui.keyguard.ui.composable
-import android.view.View
-import android.view.ViewGroup
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.combinedClickable
-import androidx.compose.foundation.gestures.awaitEachGesture
-import androidx.compose.foundation.gestures.awaitFirstDown
-import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.graphics.toComposeRect
-import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.viewinterop.AndroidView
-import androidx.core.view.isVisible
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.qualifiers.KeyguardRootView
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
-import com.android.systemui.notifications.ui.composable.NotificationStack
-import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.Edge
import com.android.systemui.scene.shared.model.SceneKey
@@ -68,8 +47,8 @@ class LockscreenScene
constructor(
@Application private val applicationScope: CoroutineScope,
private val viewModel: LockscreenSceneViewModel,
- @KeyguardRootView private val viewProvider: () -> @JvmSuppressWildcards View,
private val lockscreenContent: Lazy<LockscreenContent>,
+ private val viewBasedLockscreenContent: Lazy<ViewBasedLockscreenContent>,
) : ComposableScene {
override val key = SceneKey.Lockscreen
@@ -93,9 +72,8 @@ constructor(
modifier: Modifier,
) {
LockscreenScene(
- viewProvider = viewProvider,
- viewModel = viewModel,
lockscreenContent = lockscreenContent,
+ viewBasedLockscreenContent = viewBasedLockscreenContent,
modifier = modifier,
)
}
@@ -116,98 +94,21 @@ constructor(
@Composable
private fun SceneScope.LockscreenScene(
- viewProvider: () -> View,
- viewModel: LockscreenSceneViewModel,
lockscreenContent: Lazy<LockscreenContent>,
+ viewBasedLockscreenContent: Lazy<ViewBasedLockscreenContent>,
modifier: Modifier = Modifier,
) {
- fun findSettingsMenu(): View {
- return viewProvider().requireViewById(R.id.keyguard_settings_button)
- }
-
- Box(
- modifier = modifier,
- ) {
- LongPressSurface(
- viewModel = viewModel.longPress,
- isSettingsMenuVisible = { findSettingsMenu().isVisible },
- settingsMenuBounds = {
- val bounds = android.graphics.Rect()
- findSettingsMenu().getHitRect(bounds)
- bounds.toComposeRect()
- },
- modifier = Modifier.fillMaxSize(),
- )
-
- if (UseLockscreenContent) {
- lockscreenContent
- .get()
- .Content(
- modifier = Modifier.fillMaxSize(),
- )
- } else {
- AndroidView(
- factory = { _ ->
- val keyguardRootView = viewProvider()
- // Remove the KeyguardRootView from any parent it might already have in legacy
- // code just in case (a view can't have two parents).
- (keyguardRootView.parent as? ViewGroup)?.removeView(keyguardRootView)
- keyguardRootView
- },
- modifier = Modifier.fillMaxSize(),
+ if (UseLockscreenContent) {
+ lockscreenContent
+ .get()
+ .Content(
+ modifier = modifier.fillMaxSize(),
+ )
+ } else {
+ with(viewBasedLockscreenContent.get()) {
+ Content(
+ modifier = modifier.fillMaxSize(),
)
- }
-
- val notificationStackPosition by viewModel.keyguardRoot.notificationBounds.collectAsState()
-
- Layout(
- modifier = Modifier.fillMaxSize(),
- content = {
- NotificationStack(
- viewModel = viewModel.notifications,
- isScrimVisible = false,
- )
- }
- ) { measurables, constraints ->
- check(measurables.size == 1)
- val height = notificationStackPosition.height.toInt()
- val childConstraints = constraints.copy(minHeight = height, maxHeight = height)
- val placeable = measurables[0].measure(childConstraints)
- layout(constraints.maxWidth, constraints.maxHeight) {
- val start = (constraints.maxWidth - placeable.measuredWidth) / 2
- placeable.placeRelative(x = start, y = notificationStackPosition.top.toInt())
- }
}
}
}
-
-@Composable
-private fun LongPressSurface(
- viewModel: KeyguardLongPressViewModel,
- isSettingsMenuVisible: () -> Boolean,
- settingsMenuBounds: () -> Rect,
- modifier: Modifier = Modifier,
-) {
- val isEnabled: Boolean by viewModel.isLongPressHandlingEnabled.collectAsState(initial = false)
-
- Box(
- modifier =
- modifier
- .combinedClickable(
- enabled = isEnabled,
- onLongClick = viewModel::onLongPress,
- onClick = {},
- )
- .pointerInput(Unit) {
- awaitEachGesture {
- val pointerInputChange = awaitFirstDown()
- if (
- isSettingsMenuVisible() &&
- !settingsMenuBounds().contains(pointerInputChange.position)
- ) {
- viewModel.onTouchedOutside()
- }
- }
- },
- )
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/ViewBasedLockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/ViewBasedLockscreenContent.kt
new file mode 100644
index 000000000000..976161b3beb7
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/ViewBasedLockscreenContent.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.keyguard.ui.composable
+
+import android.graphics.Rect
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.toComposeRect
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.onPlaced
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.core.view.isVisible
+import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.qualifiers.KeyguardRootView
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
+import com.android.systemui.notifications.ui.composable.NotificationStack
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/**
+ * Renders the content of the lockscreen.
+ *
+ * This is different from [LockscreenContent] (which is pure compose) and uses a view-based
+ * implementation of the lockscreen scene content that relies on [KeyguardRootView].
+ *
+ * TODO(b/316211368): remove this once [LockscreenContent] is feature complete.
+ */
+class ViewBasedLockscreenContent
+@Inject
+constructor(
+ private val viewModel: LockscreenSceneViewModel,
+ @KeyguardRootView private val viewProvider: () -> @JvmSuppressWildcards View,
+) {
+ @Composable
+ fun SceneScope.Content(
+ modifier: Modifier = Modifier,
+ ) {
+ fun findSettingsMenu(): View {
+ return viewProvider().requireViewById(R.id.keyguard_settings_button)
+ }
+
+ LockscreenLongPress(
+ viewModel = viewModel.longPress,
+ modifier = modifier,
+ ) { onSettingsMenuPlaced ->
+ AndroidView(
+ factory = { _ ->
+ val keyguardRootView = viewProvider()
+ // Remove the KeyguardRootView from any parent it might already have in legacy
+ // code just in case (a view can't have two parents).
+ (keyguardRootView.parent as? ViewGroup)?.removeView(keyguardRootView)
+ keyguardRootView
+ },
+ modifier = Modifier.fillMaxSize(),
+ )
+
+ val notificationStackPosition by
+ viewModel.keyguardRoot.notificationBounds.collectAsState()
+
+ Layout(
+ modifier =
+ Modifier.fillMaxSize().onPlaced {
+ val settingsMenuView = findSettingsMenu()
+ onSettingsMenuPlaced(
+ if (settingsMenuView.isVisible) {
+ val bounds = Rect()
+ settingsMenuView.getHitRect(bounds)
+ bounds.toComposeRect()
+ } else {
+ null
+ }
+ )
+ },
+ content = {
+ NotificationStack(
+ viewModel = viewModel.notifications,
+ isScrimVisible = false,
+ )
+ }
+ ) { measurables, constraints ->
+ check(measurables.size == 1)
+ val height = notificationStackPosition.height.toInt()
+ val childConstraints = constraints.copy(minHeight = height, maxHeight = height)
+ val placeable = measurables[0].measure(childConstraints)
+ layout(constraints.maxWidth, constraints.maxHeight) {
+ val start = (constraints.maxWidth - placeable.measuredWidth) / 2
+ placeable.placeRelative(x = start, y = notificationStackPosition.top.toInt())
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
index 7eddaaf9ba4b..86124c635684 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
@@ -24,24 +24,35 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoSet
import javax.inject.Inject
/** Renders the lockscreen scene when showing the communal glanceable hub. */
-class CommunalBlueprint @Inject constructor() : LockscreenSceneBlueprint {
+class CommunalBlueprint
+@Inject
+constructor(
+ private val viewModel: LockscreenContentViewModel,
+) : LockscreenSceneBlueprint {
override val id: String = "communal"
@Composable
override fun SceneScope.Content(modifier: Modifier) {
- Box(modifier.background(Color.Black)) {
- Text(
- text = "TODO(b/316211368): communal blueprint",
- color = Color.White,
- modifier = Modifier.align(Alignment.Center),
- )
+ LockscreenLongPress(
+ viewModel = viewModel.longPress,
+ modifier = modifier,
+ ) { _ ->
+ Box(modifier.background(Color.Black)) {
+ Text(
+ text = "TODO(b/316211368): communal blueprint",
+ color = Color.White,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index 7314453f089f..d9d98cbd2da6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -17,17 +17,20 @@
package com.android.systemui.keyguard.ui.composable.blueprint
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.IntRect
import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
import com.android.systemui.keyguard.ui.composable.section.ClockSection
import com.android.systemui.keyguard.ui.composable.section.LockSection
import com.android.systemui.keyguard.ui.composable.section.NotificationSection
+import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
@@ -51,6 +54,7 @@ constructor(
private val lockSection: LockSection,
private val ambientIndicationSection: AmbientIndicationSection,
private val bottomAreaSection: BottomAreaSection,
+ private val settingsMenuSection: SettingsMenuSection,
) : LockscreenSceneBlueprint {
override val id: String = "default"
@@ -59,102 +63,116 @@ constructor(
override fun SceneScope.Content(modifier: Modifier) {
val isUdfpsVisible = viewModel.isUdfpsVisible
- Layout(
- content = {
- // Constrained to above the lock icon.
- Column(
- modifier = Modifier.fillMaxWidth(),
- ) {
- with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
- with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
- with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
- with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
- with(notificationSection) {
- Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
- }
- if (!isUdfpsVisible) {
- with(ambientIndicationSection) {
- AmbientIndication(modifier = Modifier.fillMaxWidth())
+ LockscreenLongPress(
+ viewModel = viewModel.longPress,
+ modifier = modifier,
+ ) { onSettingsMenuPlaced ->
+ Layout(
+ content = {
+ // Constrained to above the lock icon.
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ ) {
+ with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
+ with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
+ with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
+ with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
+ with(notificationSection) {
+ Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
+ }
+ if (!isUdfpsVisible) {
+ with(ambientIndicationSection) {
+ AmbientIndication(modifier = Modifier.fillMaxWidth())
+ }
}
}
- }
- with(lockSection) { LockIcon() }
+ with(lockSection) { LockIcon() }
- // Aligned to bottom and constrained to below the lock icon.
- Column(modifier = Modifier.fillMaxWidth()) {
- if (isUdfpsVisible) {
- with(ambientIndicationSection) {
- AmbientIndication(modifier = Modifier.fillMaxWidth())
+ // Aligned to bottom and constrained to below the lock icon.
+ Column(modifier = Modifier.fillMaxWidth()) {
+ if (isUdfpsVisible) {
+ with(ambientIndicationSection) {
+ AmbientIndication(modifier = Modifier.fillMaxWidth())
+ }
}
- }
- with(bottomAreaSection) { IndicationArea(modifier = Modifier.fillMaxWidth()) }
- }
+ with(bottomAreaSection) {
+ IndicationArea(modifier = Modifier.fillMaxWidth())
+ }
+ }
- // Aligned to bottom and NOT constrained by the lock icon.
- with(bottomAreaSection) {
- Shortcut(isStart = true, applyPadding = true)
- Shortcut(isStart = false, applyPadding = true)
- }
- },
- modifier = modifier,
- ) { measurables, constraints ->
- check(measurables.size == 5)
- val (
- aboveLockIconMeasurable,
- lockIconMeasurable,
- belowLockIconMeasurable,
- startShortcutMeasurable,
- endShortcutMeasurable,
- ) = measurables
+ // Aligned to bottom and NOT constrained by the lock icon.
+ with(bottomAreaSection) {
+ Shortcut(isStart = true, applyPadding = true)
+ Shortcut(isStart = false, applyPadding = true)
+ }
+ with(settingsMenuSection) { SettingsMenu(onSettingsMenuPlaced) }
+ },
+ modifier = Modifier.fillMaxSize(),
+ ) { measurables, constraints ->
+ check(measurables.size == 6)
+ val aboveLockIconMeasurable = measurables[0]
+ val lockIconMeasurable = measurables[1]
+ val belowLockIconMeasurable = measurables[2]
+ val startShortcutMeasurable = measurables[3]
+ val endShortcutMeasurable = measurables[4]
+ val settingsMenuMeasurable = measurables[5]
- val noMinConstraints =
- constraints.copy(
- minWidth = 0,
- minHeight = 0,
- )
- val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
- val lockIconBounds =
- IntRect(
- left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
- top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
- right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
- bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
- )
+ val noMinConstraints =
+ constraints.copy(
+ minWidth = 0,
+ minHeight = 0,
+ )
+ val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
+ val lockIconBounds =
+ IntRect(
+ left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
+ top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
+ right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
+ bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
+ )
- val aboveLockIconPlaceable =
- aboveLockIconMeasurable.measure(
- noMinConstraints.copy(maxHeight = lockIconBounds.top)
- )
- val belowLockIconPlaceable =
- belowLockIconMeasurable.measure(
- noMinConstraints.copy(maxHeight = constraints.maxHeight - lockIconBounds.bottom)
- )
- val startShortcutPleaceable = startShortcutMeasurable.measure(noMinConstraints)
- val endShortcutPleaceable = endShortcutMeasurable.measure(noMinConstraints)
+ val aboveLockIconPlaceable =
+ aboveLockIconMeasurable.measure(
+ noMinConstraints.copy(maxHeight = lockIconBounds.top)
+ )
+ val belowLockIconPlaceable =
+ belowLockIconMeasurable.measure(
+ noMinConstraints.copy(
+ maxHeight = constraints.maxHeight - lockIconBounds.bottom
+ )
+ )
+ val startShortcutPleaceable = startShortcutMeasurable.measure(noMinConstraints)
+ val endShortcutPleaceable = endShortcutMeasurable.measure(noMinConstraints)
+ val settingsMenuPlaceable = settingsMenuMeasurable.measure(noMinConstraints)
- layout(constraints.maxWidth, constraints.maxHeight) {
- aboveLockIconPlaceable.place(
- x = 0,
- y = 0,
- )
- lockIconPlaceable.place(
- x = lockIconBounds.left,
- y = lockIconBounds.top,
- )
- belowLockIconPlaceable.place(
- x = 0,
- y = constraints.maxHeight - belowLockIconPlaceable.height,
- )
- startShortcutPleaceable.place(
- x = 0,
- y = constraints.maxHeight - startShortcutPleaceable.height,
- )
- endShortcutPleaceable.place(
- x = constraints.maxWidth - endShortcutPleaceable.width,
- y = constraints.maxHeight - endShortcutPleaceable.height,
- )
+ layout(constraints.maxWidth, constraints.maxHeight) {
+ aboveLockIconPlaceable.place(
+ x = 0,
+ y = 0,
+ )
+ lockIconPlaceable.place(
+ x = lockIconBounds.left,
+ y = lockIconBounds.top,
+ )
+ belowLockIconPlaceable.place(
+ x = 0,
+ y = constraints.maxHeight - belowLockIconPlaceable.height,
+ )
+ startShortcutPleaceable.place(
+ x = 0,
+ y = constraints.maxHeight - startShortcutPleaceable.height,
+ )
+ endShortcutPleaceable.place(
+ x = constraints.maxWidth - endShortcutPleaceable.width,
+ y = constraints.maxHeight - endShortcutPleaceable.height,
+ )
+ settingsMenuPlaceable.place(
+ x = (constraints.maxWidth - settingsMenuPlaceable.width) / 2,
+ y = constraints.maxHeight - settingsMenuPlaceable.height,
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
index 4c119c7394bf..4704f5c3d1eb 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
@@ -17,17 +17,20 @@
package com.android.systemui.keyguard.ui.composable.blueprint
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.IntRect
import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
import com.android.systemui.keyguard.ui.composable.section.ClockSection
import com.android.systemui.keyguard.ui.composable.section.LockSection
import com.android.systemui.keyguard.ui.composable.section.NotificationSection
+import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
@@ -51,6 +54,7 @@ constructor(
private val lockSection: LockSection,
private val ambientIndicationSection: AmbientIndicationSection,
private val bottomAreaSection: BottomAreaSection,
+ private val settingsMenuSection: SettingsMenuSection,
) : LockscreenSceneBlueprint {
override val id: String = "shortcuts-besides-udfps"
@@ -59,105 +63,123 @@ constructor(
override fun SceneScope.Content(modifier: Modifier) {
val isUdfpsVisible = viewModel.isUdfpsVisible
- Layout(
- content = {
- // Constrained to above the lock icon.
- Column(
- modifier = Modifier.fillMaxWidth(),
- ) {
- with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
- with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
- with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
- with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
- with(notificationSection) {
- Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
- }
- if (!isUdfpsVisible) {
- with(ambientIndicationSection) {
- AmbientIndication(modifier = Modifier.fillMaxWidth())
+ LockscreenLongPress(
+ viewModel = viewModel.longPress,
+ modifier = modifier,
+ ) { onSettingsMenuPlaced ->
+ Layout(
+ content = {
+ // Constrained to above the lock icon.
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ ) {
+ with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
+ with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
+ with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
+ with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
+ with(notificationSection) {
+ Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
+ }
+ if (!isUdfpsVisible) {
+ with(ambientIndicationSection) {
+ AmbientIndication(modifier = Modifier.fillMaxWidth())
+ }
}
}
- }
- // Constrained to the left of the lock icon (in left-to-right layouts).
- with(bottomAreaSection) { Shortcut(isStart = true, applyPadding = false) }
+ // Constrained to the left of the lock icon (in left-to-right layouts).
+ with(bottomAreaSection) { Shortcut(isStart = true, applyPadding = false) }
- with(lockSection) { LockIcon() }
+ with(lockSection) { LockIcon() }
- // Constrained to the right of the lock icon (in left-to-right layouts).
- with(bottomAreaSection) { Shortcut(isStart = false, applyPadding = false) }
+ // Constrained to the right of the lock icon (in left-to-right layouts).
+ with(bottomAreaSection) { Shortcut(isStart = false, applyPadding = false) }
- // Aligned to bottom and constrained to below the lock icon.
- Column(modifier = Modifier.fillMaxWidth()) {
- if (isUdfpsVisible) {
- with(ambientIndicationSection) {
- AmbientIndication(modifier = Modifier.fillMaxWidth())
+ // Aligned to bottom and constrained to below the lock icon.
+ Column(modifier = Modifier.fillMaxWidth()) {
+ if (isUdfpsVisible) {
+ with(ambientIndicationSection) {
+ AmbientIndication(modifier = Modifier.fillMaxWidth())
+ }
+ }
+
+ with(bottomAreaSection) {
+ IndicationArea(modifier = Modifier.fillMaxWidth())
}
}
- with(bottomAreaSection) { IndicationArea(modifier = Modifier.fillMaxWidth()) }
+ // Aligned to bottom and NOT constrained by the lock icon.
+ with(settingsMenuSection) { SettingsMenu(onSettingsMenuPlaced) }
+ },
+ modifier = Modifier.fillMaxSize(),
+ ) { measurables, constraints ->
+ check(measurables.size == 6)
+ val aboveLockIconMeasurable = measurables[0]
+ val startSideShortcutMeasurable = measurables[1]
+ val lockIconMeasurable = measurables[2]
+ val endSideShortcutMeasurable = measurables[3]
+ val belowLockIconMeasurable = measurables[4]
+ val settingsMenuMeasurable = measurables[5]
+
+ val noMinConstraints =
+ constraints.copy(
+ minWidth = 0,
+ minHeight = 0,
+ )
+
+ val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
+ val lockIconBounds =
+ IntRect(
+ left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
+ top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
+ right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
+ bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
+ )
+
+ val aboveLockIconPlaceable =
+ aboveLockIconMeasurable.measure(
+ noMinConstraints.copy(maxHeight = lockIconBounds.top)
+ )
+ val startSideShortcutPlaceable =
+ startSideShortcutMeasurable.measure(noMinConstraints)
+ val endSideShortcutPlaceable = endSideShortcutMeasurable.measure(noMinConstraints)
+ val belowLockIconPlaceable =
+ belowLockIconMeasurable.measure(
+ noMinConstraints.copy(
+ maxHeight = constraints.maxHeight - lockIconBounds.bottom
+ )
+ )
+ val settingsMenuPlaceable = settingsMenuMeasurable.measure(noMinConstraints)
+
+ layout(constraints.maxWidth, constraints.maxHeight) {
+ aboveLockIconPlaceable.place(
+ x = 0,
+ y = 0,
+ )
+ startSideShortcutPlaceable.placeRelative(
+ x = lockIconBounds.left / 2 - startSideShortcutPlaceable.width / 2,
+ y = lockIconBounds.center.y - startSideShortcutPlaceable.height / 2,
+ )
+ lockIconPlaceable.place(
+ x = lockIconBounds.left,
+ y = lockIconBounds.top,
+ )
+ endSideShortcutPlaceable.placeRelative(
+ x =
+ lockIconBounds.right +
+ (constraints.maxWidth - lockIconBounds.right) / 2 -
+ endSideShortcutPlaceable.width / 2,
+ y = lockIconBounds.center.y - endSideShortcutPlaceable.height / 2,
+ )
+ belowLockIconPlaceable.place(
+ x = 0,
+ y = constraints.maxHeight - belowLockIconPlaceable.height,
+ )
+ settingsMenuPlaceable.place(
+ x = (constraints.maxWidth - settingsMenuPlaceable.width) / 2,
+ y = constraints.maxHeight - settingsMenuPlaceable.height,
+ )
}
- },
- modifier = modifier,
- ) { measurables, constraints ->
- check(measurables.size == 5)
- val (
- aboveLockIconMeasurable,
- startSideShortcutMeasurable,
- lockIconMeasurable,
- endSideShortcutMeasurable,
- belowLockIconMeasurable,
- ) = measurables
-
- val noMinConstraints =
- constraints.copy(
- minWidth = 0,
- minHeight = 0,
- )
-
- val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
- val lockIconBounds =
- IntRect(
- left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
- top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
- right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
- bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
- )
-
- val aboveLockIconPlaceable =
- aboveLockIconMeasurable.measure(
- noMinConstraints.copy(maxHeight = lockIconBounds.top)
- )
- val startSideShortcutPlaceable = startSideShortcutMeasurable.measure(noMinConstraints)
- val endSideShortcutPlaceable = endSideShortcutMeasurable.measure(noMinConstraints)
- val belowLockIconPlaceable =
- belowLockIconMeasurable.measure(
- noMinConstraints.copy(maxHeight = constraints.maxHeight - lockIconBounds.bottom)
- )
-
- layout(constraints.maxWidth, constraints.maxHeight) {
- aboveLockIconPlaceable.place(
- x = 0,
- y = 0,
- )
- startSideShortcutPlaceable.placeRelative(
- x = lockIconBounds.left / 2 - startSideShortcutPlaceable.width / 2,
- y = lockIconBounds.center.y - startSideShortcutPlaceable.height / 2,
- )
- lockIconPlaceable.place(
- x = lockIconBounds.left,
- y = lockIconBounds.top,
- )
- endSideShortcutPlaceable.placeRelative(
- x =
- lockIconBounds.right + (constraints.maxWidth - lockIconBounds.right) / 2 -
- endSideShortcutPlaceable.width / 2,
- y = lockIconBounds.center.y - endSideShortcutPlaceable.height / 2,
- )
- belowLockIconPlaceable.place(
- x = 0,
- y = constraints.maxHeight - belowLockIconPlaceable.height,
- )
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
index 7545d5fcc2b3..fdf11668ae76 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
@@ -24,6 +24,8 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoSet
@@ -33,18 +35,27 @@ import javax.inject.Inject
* Renders the lockscreen scene when showing with a split shade (e.g. unfolded foldable and/or
* tablet form factor).
*/
-class SplitShadeBlueprint @Inject constructor() : LockscreenSceneBlueprint {
+class SplitShadeBlueprint
+@Inject
+constructor(
+ private val viewModel: LockscreenContentViewModel,
+) : LockscreenSceneBlueprint {
override val id: String = "split-shade"
@Composable
override fun SceneScope.Content(modifier: Modifier) {
- Box(modifier.background(Color.Black)) {
- Text(
- text = "TODO(b/316211368): split shade blueprint",
- color = Color.White,
- modifier = Modifier.align(Alignment.Center),
- )
+ LockscreenLongPress(
+ viewModel = viewModel.longPress,
+ modifier = modifier,
+ ) { _ ->
+ Box(modifier.background(Color.Black)) {
+ Text(
+ text = "TODO(b/316211368): split shade blueprint",
+ color = Color.White,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
index d93863dd61e5..2a6bea75791c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
@@ -19,25 +19,33 @@ package com.android.systemui.keyguard.ui.composable.section
import android.content.Context
import android.util.DisplayMetrics
import android.view.WindowManager
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.layout
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntRect
+import androidx.compose.ui.viewinterop.AndroidView
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
+import com.android.keyguard.LockIconView
+import com.android.keyguard.LockIconViewController
+import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.biometrics.AuthController
+import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.ui.binder.DeviceEntryIconViewBinder
import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
+import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
+import com.android.systemui.statusbar.VibratorHelper
+import dagger.Lazy
import javax.inject.Inject
class LockSection
@@ -46,48 +54,70 @@ constructor(
private val windowManager: WindowManager,
private val authController: AuthController,
private val featureFlags: FeatureFlagsClassic,
+ private val lockIconViewController: Lazy<LockIconViewController>,
+ private val deviceEntryIconViewModel: Lazy<DeviceEntryIconViewModel>,
+ private val deviceEntryForegroundViewModel: Lazy<DeviceEntryForegroundViewModel>,
+ private val deviceEntryBackgroundViewModel: Lazy<DeviceEntryBackgroundViewModel>,
+ private val falsingManager: Lazy<FalsingManager>,
+ private val vibratorHelper: Lazy<VibratorHelper>,
) {
@Composable
fun SceneScope.LockIcon(modifier: Modifier = Modifier) {
- MovableElement(
- key = LockIconElementKey,
- modifier = modifier,
- ) {
- val context = LocalContext.current
- Box(
- modifier =
- Modifier.background(Color.Red).layout { measurable, _ ->
- val lockIconBounds = lockIconBounds(context)
- val placeable =
- measurable.measure(
- Constraints.fixed(
- width = lockIconBounds.width,
- height = lockIconBounds.height,
- )
+ if (!keyguardBottomAreaRefactor() && !DeviceEntryUdfpsRefactor.isEnabled) {
+ return
+ }
+
+ val context = LocalContext.current
+
+ AndroidView(
+ factory = { context ->
+ val view =
+ if (DeviceEntryUdfpsRefactor.isEnabled) {
+ DeviceEntryIconView(context, null).apply {
+ id = R.id.device_entry_icon_view
+ DeviceEntryIconViewBinder.bind(
+ this,
+ deviceEntryIconViewModel.get(),
+ deviceEntryForegroundViewModel.get(),
+ deviceEntryBackgroundViewModel.get(),
+ falsingManager.get(),
+ vibratorHelper.get(),
)
- layout(
- width = placeable.width,
- height = placeable.height,
- alignmentLines =
- mapOf(
- BlueprintAlignmentLines.LockIcon.Left to lockIconBounds.left,
- BlueprintAlignmentLines.LockIcon.Top to lockIconBounds.top,
- BlueprintAlignmentLines.LockIcon.Right to lockIconBounds.right,
- BlueprintAlignmentLines.LockIcon.Bottom to
- lockIconBounds.bottom,
- ),
- ) {
- placeable.place(0, 0)
}
- },
- ) {
- Text(
- text = "TODO(b/316211368): Lock",
- color = Color.White,
- modifier = Modifier.align(Alignment.Center),
- )
- }
- }
+ } else {
+ // keyguardBottomAreaRefactor()
+ LockIconView(context, null).apply {
+ id = R.id.lock_icon_view
+ lockIconViewController.get().setLockIconView(this)
+ }
+ }
+ view
+ },
+ modifier =
+ modifier.element(LockIconElementKey).layout { measurable, _ ->
+ val lockIconBounds = lockIconBounds(context)
+ val placeable =
+ measurable.measure(
+ Constraints.fixed(
+ width = lockIconBounds.width,
+ height = lockIconBounds.height,
+ )
+ )
+ layout(
+ width = placeable.width,
+ height = placeable.height,
+ alignmentLines =
+ mapOf(
+ BlueprintAlignmentLines.LockIcon.Left to lockIconBounds.left,
+ BlueprintAlignmentLines.LockIcon.Top to lockIconBounds.top,
+ BlueprintAlignmentLines.LockIcon.Right to lockIconBounds.right,
+ BlueprintAlignmentLines.LockIcon.Bottom to lockIconBounds.bottom,
+ ),
+ ) {
+ placeable.place(0, 0)
+ }
+ },
+ )
}
/**
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index f135be260cbd..c547e2b93158 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -16,36 +16,25 @@
package com.android.systemui.keyguard.ui.composable.section
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.notifications.ui.composable.NotificationStack
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
-class NotificationSection @Inject constructor() {
+class NotificationSection
+@Inject
+constructor(
+ private val viewModel: NotificationsPlaceholderViewModel,
+) {
@Composable
fun SceneScope.Notifications(modifier: Modifier = Modifier) {
- MovableElement(
- key = NotificationsElementKey,
+ NotificationStack(
+ viewModel = viewModel,
+ isScrimVisible = false,
modifier = modifier,
- ) {
- Box(
- modifier = Modifier.fillMaxSize().background(Color.Yellow),
- ) {
- Text(
- text = "TODO(b/316211368): Notifications",
- color = Color.White,
- modifier = Modifier.align(Alignment.Center),
- )
- }
- }
+ )
}
}
-
-private val NotificationsElementKey = ElementKey("Notifications")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SettingsMenuSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SettingsMenuSection.kt
new file mode 100644
index 000000000000..44b0535efb57
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SettingsMenuSection.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.keyguard.ui.composable.section
+
+import android.view.LayoutInflater
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.layout.onPlaced
+import androidx.compose.ui.layout.positionInParent
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.toSize
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.core.view.isVisible
+import com.android.systemui.keyguard.ui.binder.KeyguardSettingsViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.VibratorHelper
+import javax.inject.Inject
+import kotlinx.coroutines.DisposableHandle
+
+class SettingsMenuSection
+@Inject
+constructor(
+ private val viewModel: KeyguardSettingsMenuViewModel,
+ private val longPressViewModel: KeyguardLongPressViewModel,
+ private val vibratorHelper: VibratorHelper,
+ private val activityStarter: ActivityStarter,
+) {
+ @Composable
+ @SuppressWarnings("InflateParams") // null is passed into the inflate call, on purpose.
+ fun SettingsMenu(
+ onPlaced: (Rect?) -> Unit,
+ modifier: Modifier = Modifier,
+ ) {
+ val (disposableHandle, setDisposableHandle) =
+ remember { mutableStateOf<DisposableHandle?>(null) }
+ AndroidView(
+ factory = { context ->
+ LayoutInflater.from(context)
+ .inflate(
+ R.layout.keyguard_settings_popup_menu,
+ null,
+ )
+ .apply {
+ isVisible = false
+ alpha = 0f
+
+ setDisposableHandle(
+ KeyguardSettingsViewBinder.bind(
+ view = this,
+ viewModel = viewModel,
+ longPressViewModel = longPressViewModel,
+ rootViewModel = null,
+ vibratorHelper = vibratorHelper,
+ activityStarter = activityStarter,
+ )
+ )
+ }
+ },
+ onRelease = { disposableHandle?.dispose() },
+ modifier =
+ modifier
+ .padding(
+ bottom = dimensionResource(R.dimen.keyguard_affordance_vertical_offset),
+ )
+ .padding(
+ horizontal =
+ dimensionResource(R.dimen.keyguard_affordance_horizontal_offset),
+ )
+ .onPlaced { coordinates ->
+ onPlaced(
+ if (!coordinates.size.toSize().isEmpty()) {
+ Rect(coordinates.positionInParent(), coordinates.size.toSize())
+ } else {
+ null
+ }
+ )
+ },
+ )
+ }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
index 26da1f025544..4b21105a20a0 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt
@@ -47,7 +47,7 @@ class NotificationSettingsRepository(
suspend fun setShowNotificationsOnLockscreenEnabled(enabled: Boolean) {
withContext(backgroundDispatcher) {
- secureSettingsRepository.set(
+ secureSettingsRepository.setInt(
name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
value = if (enabled) 1 else 0,
)
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
index 7ef16a8a4b23..754d5dc0c9c6 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
@@ -37,15 +37,17 @@ interface SecureSettingsRepository {
): Flow<Int>
/** Updates the value of the setting with the given name. */
- suspend fun set(
+ suspend fun setInt(
name: String,
value: Int,
)
- suspend fun get(
+ suspend fun getInt(
name: String,
defaultValue: Int = 0,
): Int
+
+ suspend fun getString(name: String): String?
}
class SecureSettingsRepositoryImpl(
@@ -80,7 +82,7 @@ class SecureSettingsRepositoryImpl(
.flowOn(backgroundDispatcher)
}
- override suspend fun set(name: String, value: Int) {
+ override suspend fun setInt(name: String, value: Int) {
withContext(backgroundDispatcher) {
Settings.Secure.putInt(
contentResolver,
@@ -90,7 +92,7 @@ class SecureSettingsRepositoryImpl(
}
}
- override suspend fun get(name: String, defaultValue: Int): Int {
+ override suspend fun getInt(name: String, defaultValue: Int): Int {
return withContext(backgroundDispatcher) {
Settings.Secure.getInt(
contentResolver,
@@ -99,4 +101,13 @@ class SecureSettingsRepositoryImpl(
)
}
}
+
+ override suspend fun getString(name: String): String? {
+ return withContext(backgroundDispatcher) {
+ Settings.Secure.getString(
+ contentResolver,
+ name,
+ )
+ }
+ }
}
diff --git a/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt b/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt
index 1c86a0745751..37b97926afa4 100644
--- a/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt
+++ b/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt
@@ -28,11 +28,15 @@ class FakeSecureSettingsRepository : SecureSettingsRepository {
return settings.map { it.getOrDefault(name, defaultValue.toString()) }.map { it.toInt() }
}
- override suspend fun set(name: String, value: Int) {
+ override suspend fun setInt(name: String, value: Int) {
settings.value = settings.value.toMutableMap().apply { this[name] = value.toString() }
}
- override suspend fun get(name: String, defaultValue: Int): Int {
+ override suspend fun getInt(name: String, defaultValue: Int): Int {
return settings.value[name]?.toInt() ?: defaultValue
}
+
+ override suspend fun getString(name: String): String? {
+ return settings.value[name]
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
index da97a1283261..1c1335f0db4d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -923,14 +923,14 @@ public class AuthControllerTest extends SysuiTestCase {
doReturn(500).when(mResources)
.getDimensionPixelSize(eq(com.android.systemui.res.R.dimen
.physical_fingerprint_sensor_center_screen_location_y));
- mAuthController.onConfigurationChanged(null /* newConfig */);
+ mAuthController.onConfigChanged(null /* newConfig */);
final Point firstFpLocation = mAuthController.getFingerprintSensorLocation();
doReturn(1000).when(mResources)
.getDimensionPixelSize(eq(com.android.systemui.res.R.dimen
.physical_fingerprint_sensor_center_screen_location_y));
- mAuthController.onConfigurationChanged(null /* newConfig */);
+ mAuthController.onConfigChanged(null /* newConfig */);
assertNotSame(firstFpLocation, mAuthController.getFingerprintSensorLocation());
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index a726b7c2b075..b0beab932e21 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -47,6 +47,7 @@ import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.phone.SystemUIDialogManager
@@ -54,7 +55,6 @@ import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
-import com.android.systemui.util.settings.SecureSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.Before
@@ -101,7 +101,6 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
@Mock
private lateinit var unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController
@Mock private lateinit var udfpsDisplayMode: UdfpsDisplayModeProvider
- @Mock private lateinit var secureSettings: SecureSettings
@Mock private lateinit var controllerCallback: IUdfpsOverlayControllerCallback
@Mock private lateinit var udfpsController: UdfpsController
@Mock private lateinit var udfpsView: UdfpsView
@@ -117,6 +116,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
@Mock
private lateinit var udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate
@Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+ @Mock private lateinit var shadeInteractor: ShadeInteractor
@Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true }
@@ -174,6 +174,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
mSelectedUserInteractor,
{ deviceEntryUdfpsTouchOverlayViewModel },
{ defaultUdfpsTouchOverlayViewModel },
+ shadeInteractor
)
block()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index a4b55e765c92..e5da1f86a841 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -94,6 +94,7 @@ import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -205,6 +206,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Mock
private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
@Mock
+ private ShadeInteractor mShadeInteractor;
+ @Mock
private SinglePointerTouchProcessor mSinglePointerTouchProcessor;
@Mock
private SessionTracker mSessionTracker;
@@ -328,6 +331,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
mActivityLaunchAnimator,
mBiometricExecutor,
mPrimaryBouncerInteractor,
+ mShadeInteractor,
mSinglePointerTouchProcessor,
mSessionTracker,
mAlternateBouncerInteractor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
index ac16c135e6a1..13b53a896b70 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
@@ -36,6 +36,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
@@ -70,6 +71,7 @@ public class UdfpsKeyguardViewLegacyControllerBaseTest extends SysuiTestCase {
protected @Mock UdfpsController mUdfpsController;
protected @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor;
+ protected @Mock ShadeInteractor mShadeInteractor;
protected @Mock AlternateBouncerInteractor mAlternateBouncerInteractor;
protected @Mock UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
protected @Mock SelectedUserInteractor mSelectedUserInteractor;
@@ -149,7 +151,8 @@ public class UdfpsKeyguardViewLegacyControllerBaseTest extends SysuiTestCase {
mAlternateBouncerInteractor,
mUdfpsKeyguardAccessibilityDelegate,
mSelectedUserInteractor,
- mKeyguardTransitionInteractor);
+ mKeyguardTransitionInteractor,
+ mShadeInteractor);
return controller;
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index 182712a13174..ddaa4889d6f3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -208,11 +208,11 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
val repository = initCommunalWidgetRepository()
runCurrent()
- val ids = listOf(104, 103, 101)
- repository.updateWidgetOrder(ids)
+ val widgetIdToPriorityMap = mapOf(104 to 1, 103 to 2, 101 to 3)
+ repository.updateWidgetOrder(widgetIdToPriorityMap)
runCurrent()
- verify(communalWidgetDao).updateWidgetOrder(ids)
+ verify(communalWidgetDao).updateWidgetOrder(widgetIdToPriorityMap)
}
@Test
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
index 557fbf21d5c4..e6122a094707 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
@@ -43,7 +43,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<FrameLayout
- android:id="@+id/status_view_media_container"
+ android:id="@id/status_view_media_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="@dimen/qs_media_padding"
diff --git a/packages/SystemUI/res/drawable/connected_display_dialog_bg.xml b/packages/SystemUI/res/drawable/connected_display_dialog_bg.xml
new file mode 100644
index 000000000000..2dce37d11337
--- /dev/null
+++ b/packages/SystemUI/res/drawable/connected_display_dialog_bg.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners
+ android:topLeftRadius="28dp"
+ android:topRightRadius="28dp"/>
+ <solid android:color="?android:attr/colorBackground" />
+</shape> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/connected_display_dialog.xml b/packages/SystemUI/res/layout/connected_display_dialog.xml
index 8d7f7ebd82dc..a71782b0a109 100644
--- a/packages/SystemUI/res/layout/connected_display_dialog.xml
+++ b/packages/SystemUI/res/layout/connected_display_dialog.xml
@@ -22,7 +22,7 @@
android:orientation="vertical"
android:paddingHorizontal="@dimen/dialog_side_padding"
android:paddingTop="@dimen/dialog_top_padding"
- android:background="@*android:drawable/bottomsheet_background"
+ android:background="@drawable/connected_display_dialog_bg"
android:paddingBottom="@dimen/dialog_bottom_padding">
<ImageView
@@ -40,7 +40,7 @@
android:id="@+id/connected_display_dialog_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/screenrecord_title_margin_top"
+ android:layout_marginTop="16dp"
android:gravity="center"
android:text="@string/connected_display_dialog_start_mirroring"
android:textAppearance="@style/TextAppearance.Dialog.Title" />
@@ -51,13 +51,14 @@
android:layout_height="wrap_content"
android:gravity="center"
android:visibility="gone"
+ android:layout_marginTop="16dp"
android:text="@string/connected_display_dialog_dual_display_stop_warning"
android:textAppearance="@style/TextAppearance.Dialog.Body" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/screenrecord_buttons_margin_top"
+ android:layout_marginTop="16dp"
android:orientation="horizontal">
<Button
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index d511caba941b..80725c2bd4b5 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -225,6 +225,8 @@
<item type="id" name="communal_tutorial_indicator" />
<item type="id" name="nssl_placeholder_barrier_bottom" />
<item type="id" name="ambient_indication_container" />
+ <item type="id" name="status_view_media_container" />
+ <item type="id" name="smart_space_barrier_bottom" />
<!-- Privacy dialog -->
<item type="id" name="privacy_dialog_close_app_button" />
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index f4b25a701825..e7eb984746e8 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3265,7 +3265,7 @@
<!--- Title of the dialog appearing when an external display is connected, asking whether to start mirroring [CHAR LIMIT=NONE]-->
<string name="connected_display_dialog_start_mirroring">Mirror to external display?</string>
<!--- Body of the mirroring dialog, shown when dual display is enabled. This signals that enabling mirroring will stop concurrent displays on a foldable device. [CHAR LIMIT=NONE]-->
- <string name="connected_display_dialog_dual_display_stop_warning">Any dual screen activity currently running will be stopped</string>
+ <string name="connected_display_dialog_dual_display_stop_warning">Your inner display will be mirrored. Your front display will be turned off.</string>
<!--- Label of the "enable display" button of the dialog appearing when an external display is connected [CHAR LIMIT=NONE]-->
<string name="mirror_display">Mirror display</string>
<!--- Label of the dismiss button of the dialog appearing when an external display is connected [CHAR LIMIT=NONE]-->
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 5b59e7da2487..2b4117866254 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -34,6 +34,9 @@ java_library {
srcs: [
":statslog-SystemUI-java-gen",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
android_library {
@@ -70,6 +73,9 @@ android_library {
min_sdk_version: "current",
plugins: ["dagger2-compiler"],
kotlincflags: ["-Xjvm-default=all"],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
java_library {
@@ -81,6 +87,9 @@ java_library {
static_kotlin_stdlib: false,
java_version: "1.8",
min_sdk_version: "current",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
java_library {
@@ -100,4 +109,7 @@ java_library {
},
java_version: "1.8",
min_sdk_version: "current",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
index 92f66902c674..387f2e1aa430 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
@@ -101,7 +101,7 @@ public class PluginInstance<T extends Plugin> implements PluginLifecycleManager
}
/** Alerts listener and plugin that the plugin has been created. */
- public void onCreate() {
+ public synchronized void onCreate() {
boolean loadPlugin = mListener.onPluginAttached(this);
if (!loadPlugin) {
if (mPlugin != null) {
@@ -128,7 +128,7 @@ public class PluginInstance<T extends Plugin> implements PluginLifecycleManager
}
/** Alerts listener and plugin that the plugin is being shutdown. */
- public void onDestroy() {
+ public synchronized void onDestroy() {
logDebug("onDestroy");
unloadPlugin();
mListener.onPluginDetached(this);
@@ -143,12 +143,13 @@ public class PluginInstance<T extends Plugin> implements PluginLifecycleManager
/**
* Loads and creates the plugin if it does not exist.
*/
- public void loadPlugin() {
+ public synchronized void loadPlugin() {
if (mPlugin != null) {
logDebug("Load request when already loaded");
return;
}
+ // Both of these calls take about 1 - 1.5 seconds in test runs
mPlugin = mPluginFactory.createPlugin();
mPluginContext = mPluginFactory.createPluginContext();
if (mPlugin == null || mPluginContext == null) {
@@ -171,7 +172,7 @@ public class PluginInstance<T extends Plugin> implements PluginLifecycleManager
*
* This will free the associated memory if there are not other references.
*/
- public void unloadPlugin() {
+ public synchronized void unloadPlugin() {
if (mPlugin == null) {
logDebug("Unload request when already unloaded");
return;
diff --git a/packages/SystemUI/src/com/android/systemui/CoreStartable.java b/packages/SystemUI/src/com/android/systemui/CoreStartable.java
index c07a4d26f476..7295936ddc7c 100644
--- a/packages/SystemUI/src/com/android/systemui/CoreStartable.java
+++ b/packages/SystemUI/src/com/android/systemui/CoreStartable.java
@@ -16,8 +16,6 @@
package com.android.systemui;
-import android.content.res.Configuration;
-
import androidx.annotation.NonNull;
import java.io.PrintWriter;
@@ -42,13 +40,6 @@ public interface CoreStartable extends Dumpable {
/** Main entry point for implementations. Called shortly after SysUI startup. */
void start();
- /** Called when the device configuration changes. This will not be called before
- * {@link #start()}, but it could be called before {@link #onBootCompleted()}.
- *
- * @see android.app.Application#onConfigurationChanged(Configuration) */
- default void onConfigurationChanged(Configuration newConfig) {
- }
-
@Override
default void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
}
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 008de4387c4a..e03c62783475 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -89,6 +89,7 @@ import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.commandline.CommandRegistry;
import com.android.systemui.statusbar.events.PrivacyDotViewController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.ThreadFactory;
import com.android.systemui.util.settings.SecureSettings;
@@ -109,7 +110,8 @@ import javax.inject.Inject;
* for antialiasing and emulation purposes.
*/
@SysUISingleton
-public class ScreenDecorations implements CoreStartable, Dumpable {
+public class ScreenDecorations implements
+ CoreStartable, ConfigurationController.ConfigurationListener, Dumpable {
private static final boolean DEBUG_LOGGING = false;
private static final String TAG = "ScreenDecorations";
@@ -575,7 +577,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable {
if (mPendingManualConfigUpdate) {
mPendingManualConfigUpdate = false;
- onConfigurationChanged(mContext.getResources().getConfiguration());
+ onConfigChanged(mContext.getResources().getConfiguration());
}
}
}
@@ -1062,7 +1064,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable {
}
@Override
- public void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigChanged(Configuration newConfig) {
if (DEBUG_DISABLE_SCREEN_DECORATIONS) {
Log.i(TAG, "ScreenDecorations is disabled");
return;
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorationsModule.kt b/packages/SystemUI/src/com/android/systemui/ScreenDecorationsModule.kt
new file mode 100644
index 000000000000..044312bcfe14
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorationsModule.kt
@@ -0,0 +1,38 @@
+/*
+ * 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
+
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
+
+@Module
+interface ScreenDecorationsModule {
+ /** Start ScreenDecorations. */
+ @Binds
+ @IntoMap
+ @ClassKey(ScreenDecorations::class)
+ fun bindScreenDecorationsCoreStartable(impl: ScreenDecorations): CoreStartable
+
+ /** Listen to config changes for ScreenDecorations. */
+ @Binds
+ @IntoSet
+ fun bindScreenDecorationsConfigListener(impl: ScreenDecorations): ConfigurationListener
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index c3f64803758b..01f6971de373 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -37,11 +37,11 @@ import android.view.SurfaceControl;
import android.view.ThreadedRenderer;
import android.view.View;
-import com.android.systemui.res.R;
import com.android.internal.protolog.common.ProtoLog;
import com.android.systemui.dagger.GlobalRootComponent;
import com.android.systemui.dagger.SysUIComponent;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.NotificationChannels;
@@ -354,19 +354,6 @@ public class SystemUIApplication extends Application implements
}
configController.onConfigurationChanged(newConfig);
Trace.endSection();
- int len = mServices.length;
- for (int i = 0; i < len; i++) {
- if (mServices[i] != null) {
- if (Trace.isEnabled()) {
- Trace.traceBegin(
- Trace.TRACE_TAG_APP,
- mServices[i].getClass().getSimpleName()
- + ".onConfigurationChanged()");
- }
- mServices[i].onConfigurationChanged(newConfig);
- Trace.endSection();
- }
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
index 3cb6314639e8..3ca95e11d789 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
@@ -77,7 +77,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
@VisibleForTesting
SparseArray<SparseArray<Float>> mUsersScales = new SparseArray();
- private static class ControllerSupplier extends
+ private static class WindowMagnificationControllerSupplier extends
DisplayIdIndexSupplier<WindowMagnificationController> {
private final Context mContext;
@@ -86,7 +86,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
private final SysUiState mSysUiState;
private final SecureSettings mSecureSettings;
- ControllerSupplier(Context context, Handler handler,
+ WindowMagnificationControllerSupplier(Context context, Handler handler,
WindowMagnifierCallback windowMagnifierCallback,
DisplayManager displayManager, SysUiState sysUiState,
SecureSettings secureSettings) {
@@ -118,7 +118,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
}
@VisibleForTesting
- DisplayIdIndexSupplier<WindowMagnificationController> mMagnificationControllerSupplier;
+ DisplayIdIndexSupplier<WindowMagnificationController> mWindowMagnificationControllerSupplier;
private static class SettingsSupplier extends
DisplayIdIndexSupplier<MagnificationSettingsController> {
@@ -168,7 +168,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
mOverviewProxyService = overviewProxyService;
mDisplayTracker = displayTracker;
mA11yLogger = a11yLogger;
- mMagnificationControllerSupplier = new ControllerSupplier(context,
+ mWindowMagnificationControllerSupplier = new WindowMagnificationControllerSupplier(context,
mHandler, mWindowMagnifierCallback,
displayManager, sysUiState, secureSettings);
mMagnificationSettingsSupplier = new SettingsSupplier(context,
@@ -196,7 +196,8 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
private void updateSysUiStateFlag() {
//TODO(b/187510533): support multi-display once SysuiState supports it.
final WindowMagnificationController controller =
- mMagnificationControllerSupplier.valueAt(mDisplayTracker.getDefaultDisplayId());
+ mWindowMagnificationControllerSupplier.valueAt(
+ mDisplayTracker.getDefaultDisplayId());
if (controller != null) {
controller.updateSysUIStateFlag();
} else {
@@ -212,7 +213,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY,
@Nullable IRemoteMagnificationAnimationCallback callback) {
final WindowMagnificationController windowMagnificationController =
- mMagnificationControllerSupplier.get(displayId);
+ mWindowMagnificationControllerSupplier.get(displayId);
if (windowMagnificationController != null) {
windowMagnificationController.enableWindowMagnification(scale, centerX, centerY,
magnificationFrameOffsetRatioX, magnificationFrameOffsetRatioY, callback);
@@ -222,7 +223,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
@MainThread
void setScaleForWindowMagnification(int displayId, float scale) {
final WindowMagnificationController windowMagnificationController =
- mMagnificationControllerSupplier.get(displayId);
+ mWindowMagnificationControllerSupplier.get(displayId);
if (windowMagnificationController != null) {
windowMagnificationController.setScale(scale);
}
@@ -231,7 +232,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
@MainThread
void moveWindowMagnifier(int displayId, float offsetX, float offsetY) {
final WindowMagnificationController windowMagnificationcontroller =
- mMagnificationControllerSupplier.get(displayId);
+ mWindowMagnificationControllerSupplier.get(displayId);
if (windowMagnificationcontroller != null) {
windowMagnificationcontroller.moveWindowMagnifier(offsetX, offsetY);
}
@@ -241,7 +242,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
void moveWindowMagnifierToPositionInternal(int displayId, float positionX, float positionY,
IRemoteMagnificationAnimationCallback callback) {
final WindowMagnificationController windowMagnificationController =
- mMagnificationControllerSupplier.get(displayId);
+ mWindowMagnificationControllerSupplier.get(displayId);
if (windowMagnificationController != null) {
windowMagnificationController.moveWindowMagnifierToPosition(positionX, positionY,
callback);
@@ -252,7 +253,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
void disableWindowMagnification(int displayId,
@Nullable IRemoteMagnificationAnimationCallback callback) {
final WindowMagnificationController windowMagnificationController =
- mMagnificationControllerSupplier.get(displayId);
+ mWindowMagnificationControllerSupplier.get(displayId);
if (windowMagnificationController != null) {
windowMagnificationController.deleteWindowMagnification(callback);
}
@@ -417,7 +418,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
@MainThread
private void onSetMagnifierSizeInternal(int displayId, int index) {
final WindowMagnificationController windowMagnificationController =
- mMagnificationControllerSupplier.get(displayId);
+ mWindowMagnificationControllerSupplier.get(displayId);
if (windowMagnificationController != null) {
windowMagnificationController.changeMagnificationSize(index);
}
@@ -426,7 +427,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
@MainThread
private void onSetDiagonalScrollingInternal(int displayId, boolean enable) {
final WindowMagnificationController windowMagnificationController =
- mMagnificationControllerSupplier.get(displayId);
+ mWindowMagnificationControllerSupplier.get(displayId);
if (windowMagnificationController != null) {
windowMagnificationController.setDiagonalScrolling(enable);
}
@@ -435,7 +436,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
@MainThread
private void onEditMagnifierSizeModeInternal(int displayId, boolean enable) {
final WindowMagnificationController windowMagnificationController =
- mMagnificationControllerSupplier.get(displayId);
+ mWindowMagnificationControllerSupplier.get(displayId);
if (windowMagnificationController != null && windowMagnificationController.isActivated()) {
windowMagnificationController.setEditMagnifierSizeMode(enable);
}
@@ -444,7 +445,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
@MainThread
private void onModeSwitchInternal(int displayId, int newMode) {
final WindowMagnificationController windowMagnificationController =
- mMagnificationControllerSupplier.get(displayId);
+ mWindowMagnificationControllerSupplier.get(displayId);
final boolean isWindowMagnifierActivated = windowMagnificationController.isActivated();
final boolean isSwitchToWindowMode = (newMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
final boolean changed = isSwitchToWindowMode ^ isWindowMagnifierActivated;
@@ -463,7 +464,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
@MainThread
private void onSettingsPanelVisibilityChangedInternal(int displayId, boolean shown) {
final WindowMagnificationController windowMagnificationController =
- mMagnificationControllerSupplier.get(displayId);
+ mWindowMagnificationControllerSupplier.get(displayId);
if (windowMagnificationController != null) {
boolean isWindowMagnifierActivated = windowMagnificationController.isActivated();
if (isWindowMagnifierActivated) {
@@ -495,7 +496,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
@Override
public void dump(PrintWriter pw, String[] args) {
pw.println(TAG);
- mMagnificationControllerSupplier.forEach(
+ mWindowMagnificationControllerSupplier.forEach(
magnificationController -> magnificationController.dump(pw));
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 7a8161eb6c3d..da49201db808 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -17,6 +17,7 @@
package com.android.systemui.accessibility;
import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS;
+
import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
import android.accessibilityservice.AccessibilityService;
@@ -57,6 +58,7 @@ import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.Assert;
@@ -71,7 +73,7 @@ import javax.inject.Inject;
* Class to register system actions with accessibility framework.
*/
@SysUISingleton
-public class SystemActions implements CoreStartable {
+public class SystemActions implements CoreStartable, ConfigurationController.ConfigurationListener {
private static final String TAG = "SystemActions";
/**
@@ -234,7 +236,7 @@ public class SystemActions implements CoreStartable {
}
@Override
- public void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigChanged(Configuration newConfig) {
final Locale locale = mContext.getResources().getConfiguration().getLocales().get(0);
if (!locale.equals(mLocale)) {
mLocale = locale;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActionsModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActionsModule.kt
new file mode 100644
index 000000000000..4d6d784fac2f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActionsModule.kt
@@ -0,0 +1,37 @@
+/*
+ * 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
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
+
+@Module
+interface SystemActionsModule {
+ /** Start SystemActions. */
+ @Binds
+ @IntoMap
+ @ClassKey(SystemActions::class)
+ fun bindSystemActionsStartable(sysui: SystemActions): CoreStartable
+
+ /** Listen to config changes for SystemActions. */
+ @Binds @IntoSet fun bindSystemActionsConfigChanges(sysui: SystemActions): ConfigurationListener
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 0bd4859eefd5..dde9f48424ea 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -1303,7 +1303,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
} else if (id == R.id.close_button) {
setEditMagnifierSizeMode(false);
} else {
- animateBounceEffect();
+ animateBounceEffectIfNeeded();
}
}
@@ -1465,7 +1465,12 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
mBounceEffectDuration = duration;
}
- private void animateBounceEffect() {
+ private void animateBounceEffectIfNeeded() {
+ if (mMirrorView == null) {
+ // run the animation only if the mirror view is not null
+ return;
+ }
+
final ObjectAnimator scaleAnimator = ObjectAnimator.ofPropertyValuesHolder(mMirrorView,
PropertyValuesHolder.ofFloat(View.SCALE_X, 1, mBounceEffectAnimationScale, 1),
PropertyValuesHolder.ofFloat(View.SCALE_Y, 1, mBounceEffectAnimationScale, 1));
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
index b1de127a4e4b..49e0df6f6afc 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
@@ -31,7 +31,7 @@ import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
* Controls the interaction between {@link MagnetizedObject} and
* {@link MagnetizedObject.MagneticTarget}.
*/
-class DismissAnimationController {
+class DragToInteractAnimationController {
private static final boolean ENABLE_FLING_TO_DISMISS_MENU = false;
private static final float COMPLETELY_OPAQUE = 1.0f;
private static final float COMPLETELY_TRANSPARENT = 0.0f;
@@ -45,7 +45,7 @@ class DismissAnimationController {
private float mMinDismissSize;
private float mSizePercent;
- DismissAnimationController(DismissView dismissView, MenuView menuView) {
+ DragToInteractAnimationController(DismissView dismissView, MenuView menuView) {
mDismissView = dismissView;
mDismissView.setPivotX(dismissView.getWidth() / 2.0f);
mDismissView.setPivotY(dismissView.getHeight() / 2.0f);
@@ -127,7 +127,7 @@ class DismissAnimationController {
* @param event that move the magnetized object which is also the menu list view.
* @return true if the location of the motion events moves within the magnetic field of a
* target, but false if didn't set
- * {@link DismissAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
+ * {@link DragToInteractAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
*/
boolean maybeConsumeMoveMotionEvent(MotionEvent event) {
return mMagnetizedObject.maybeConsumeMotionEvent(event);
@@ -140,7 +140,7 @@ class DismissAnimationController {
* @param event that move the magnetized object which is also the menu list view.
* @return true if the location of the motion events moves within the magnetic field of a
* target, but false if didn't set
- * {@link DismissAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
+ * {@link DragToInteractAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
*/
boolean maybeConsumeUpMotionEvent(MotionEvent event) {
return mMagnetizedObject.maybeConsumeMotionEvent(event);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
index 34d7cecc47b0..a2705584d76a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
@@ -73,7 +73,7 @@ class MenuAnimationController {
private final ValueAnimator mFadeOutAnimator;
private final Handler mHandler;
private boolean mIsFadeEffectEnabled;
- private DismissAnimationController.DismissCallback mDismissCallback;
+ private DragToInteractAnimationController.DismissCallback mDismissCallback;
private Runnable mSpringAnimationsEndAction;
// Cache the animations state of {@link DynamicAnimation.TRANSLATION_X} and {@link
@@ -171,7 +171,7 @@ class MenuAnimationController {
}
void setDismissCallback(
- DismissAnimationController.DismissCallback dismissCallback) {
+ DragToInteractAnimationController.DismissCallback dismissCallback) {
mDismissCallback = dismissCallback;
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
index d01590f10253..52e7b91d373e 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
@@ -40,13 +40,13 @@ class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener {
private final PointF mMenuTranslationDown = new PointF();
private boolean mIsDragging = false;
private float mTouchSlop;
- private final DismissAnimationController mDismissAnimationController;
+ private final DragToInteractAnimationController mDragToInteractAnimationController;
private Optional<Runnable> mOnActionDownEnd = Optional.empty();
MenuListViewTouchHandler(MenuAnimationController menuAnimationController,
- DismissAnimationController dismissAnimationController) {
+ DragToInteractAnimationController dragToInteractAnimationController) {
mMenuAnimationController = menuAnimationController;
- mDismissAnimationController = dismissAnimationController;
+ mDragToInteractAnimationController = dragToInteractAnimationController;
}
@Override
@@ -67,7 +67,7 @@ class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener {
mMenuTranslationDown.set(menuView.getTranslationX(), menuView.getTranslationY());
mMenuAnimationController.cancelAnimations();
- mDismissAnimationController.maybeConsumeDownMotionEvent(motionEvent);
+ mDragToInteractAnimationController.maybeConsumeDownMotionEvent(motionEvent);
mOnActionDownEnd.ifPresent(Runnable::run);
break;
@@ -78,9 +78,10 @@ class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener {
mMenuAnimationController.onDraggingStart();
}
- mDismissAnimationController.showDismissView(/* show= */ true);
+ mDragToInteractAnimationController.showDismissView(/* show= */ true);
- if (!mDismissAnimationController.maybeConsumeMoveMotionEvent(motionEvent)) {
+ if (!mDragToInteractAnimationController.maybeConsumeMoveMotionEvent(
+ motionEvent)) {
mMenuAnimationController.moveToPositionX(mMenuTranslationDown.x + dx);
mMenuAnimationController.moveToPositionYIfNeeded(
mMenuTranslationDown.y + dy);
@@ -94,17 +95,18 @@ class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener {
mIsDragging = false;
if (mMenuAnimationController.maybeMoveToEdgeAndHide(endX)) {
- mDismissAnimationController.showDismissView(/* show= */ false);
+ mDragToInteractAnimationController.showDismissView(/* show= */ false);
mMenuAnimationController.fadeOutIfEnabled();
return true;
}
- if (!mDismissAnimationController.maybeConsumeUpMotionEvent(motionEvent)) {
+ if (!mDragToInteractAnimationController.maybeConsumeUpMotionEvent(
+ motionEvent)) {
mVelocityTracker.computeCurrentVelocity(VELOCITY_UNIT_SECONDS);
mMenuAnimationController.flingMenuThenSpringToEdge(endX,
mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
- mDismissAnimationController.showDismissView(/* show= */ false);
+ mDragToInteractAnimationController.showDismissView(/* show= */ false);
}
// Avoid triggering the listener of the item.
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 ff3a9e3bd409..62d5feb7d024 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -94,7 +94,7 @@ class MenuViewLayer extends FrameLayout implements
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final IAccessibilityFloatingMenu mFloatingMenu;
private final SecureSettings mSecureSettings;
- private final DismissAnimationController mDismissAnimationController;
+ private final DragToInteractAnimationController mDragToInteractAnimationController;
private final MenuViewModel mMenuViewModel;
private final Observer<Boolean> mDockTooltipObserver =
this::onDockTooltipVisibilityChanged;
@@ -188,29 +188,30 @@ class MenuViewLayer extends FrameLayout implements
mMenuAnimationController.setSpringAnimationsEndAction(this::onSpringAnimationsEndAction);
mDismissView = new DismissView(context);
DismissViewUtils.setup(mDismissView);
- mDismissAnimationController = new DismissAnimationController(mDismissView, mMenuView);
- mDismissAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
+ mDragToInteractAnimationController = new DragToInteractAnimationController(
+ mDismissView, mMenuView);
+ mDragToInteractAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
@Override
public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
- mDismissAnimationController.animateDismissMenu(/* scaleUp= */ true);
+ mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ true);
}
@Override
public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
float velocityX, float velocityY, boolean wasFlungOut) {
- mDismissAnimationController.animateDismissMenu(/* scaleUp= */ false);
+ mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ false);
}
@Override
public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
hideMenuAndShowMessage();
mDismissView.hide();
- mDismissAnimationController.animateDismissMenu(/* scaleUp= */ false);
+ mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ false);
}
});
mMenuListViewTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController,
- mDismissAnimationController);
+ mDragToInteractAnimationController);
mMenuView.addOnItemTouchListenerToList(mMenuListViewTouchHandler);
mMenuView.setMoveToTuckedListener(this);
@@ -243,7 +244,7 @@ class MenuViewLayer extends FrameLayout implements
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
mDismissView.updateResources();
- mDismissAnimationController.updateResources();
+ mDragToInteractAnimationController.updateResources();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 5fba761b2f09..8a1a2da6cf3f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -84,6 +84,7 @@ import com.android.systemui.keyguard.data.repository.BiometricType;
import com.android.systemui.log.core.LogLevel;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.VibratorHelper;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.Execution;
@@ -114,8 +115,12 @@ import kotlinx.coroutines.CoroutineScope;
* {@link com.android.keyguard.KeyguardUpdateMonitor}
*/
@SysUISingleton
-public class AuthController implements CoreStartable, CommandQueue.Callbacks,
- AuthDialogCallback, DozeReceiver {
+public class AuthController implements
+ CoreStartable,
+ ConfigurationController.ConfigurationListener,
+ CommandQueue.Callbacks,
+ AuthDialogCallback,
+ DozeReceiver {
private static final String TAG = "AuthController";
private static final boolean DEBUG = true;
@@ -1297,7 +1302,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
}
@Override
- public void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigChanged(Configuration newConfig) {
updateSensorLocations();
// TODO(b/287311775): consider removing this to retain the UI cleanly vs re-creating
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
index 66fb8ca2832d..7d9ec08bcb3b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
@@ -23,16 +23,14 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.animation.Interpolators
import com.android.systemui.Dumpable
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dump.DumpManager
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.StatusBarState.SHADE
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.util.ViewController
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import java.io.PrintWriter
@@ -49,7 +47,7 @@ import java.io.PrintWriter
abstract class UdfpsAnimationViewController<T : UdfpsAnimationView>(
view: T,
protected val statusBarStateController: StatusBarStateController,
- protected val primaryBouncerInteractor: PrimaryBouncerInteractor,
+ protected val shadeInteractor: ShadeInteractor,
protected val dialogManager: SystemUIDialogManager,
private val dumpManager: DumpManager
) : ViewController<T>(view), Dumpable {
@@ -94,20 +92,18 @@ abstract class UdfpsAnimationViewController<T : UdfpsAnimationView>(
// can make the view not visible; and we still want to listen for events
// that may make the view visible again.
repeatOnLifecycle(Lifecycle.State.CREATED) {
- listenForBouncerExpansion(this)
+ listenForShadeExpansion(this)
}
}
}
@VisibleForTesting
- open suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job {
+ suspend fun listenForShadeExpansion(scope: CoroutineScope): Job {
return scope.launch {
- primaryBouncerInteractor.bouncerExpansion.map { 1f - it }.collect { expansion: Float ->
- if (statusBarStateController.state != SHADE) {
- notificationShadeVisible = expansion > 0f
- view.onExpansionChanged(expansion)
- updatePauseAuth()
- }
+ shadeInteractor.anyExpansion.collect { expansion ->
+ notificationShadeVisible = expansion > 0f
+ view.onExpansionChanged(expansion)
+ updatePauseAuth()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
index 03749a9dc0df..e7b0d9fd9d85 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
@@ -15,9 +15,9 @@
*/
package com.android.systemui.biometrics
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.phone.SystemUIDialogManager
/**
@@ -26,13 +26,13 @@ import com.android.systemui.statusbar.phone.SystemUIDialogManager
class UdfpsBpViewController(
view: UdfpsBpView,
statusBarStateController: StatusBarStateController,
- primaryBouncerInteractor: PrimaryBouncerInteractor,
+ shadeInteractor: ShadeInteractor,
systemUIDialogManager: SystemUIDialogManager,
dumpManager: DumpManager
) : UdfpsAnimationViewController<UdfpsBpView>(
view,
statusBarStateController,
- primaryBouncerInteractor,
+ shadeInteractor,
systemUIDialogManager,
dumpManager
) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 240728a8f1e8..2fd13b349438 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -89,6 +89,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.VibratorHelper;
@@ -162,6 +163,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
@VisibleForTesting @NonNull final BiometricDisplayListener mOrientationListener;
@NonNull private final ActivityLaunchAnimator mActivityLaunchAnimator;
@NonNull private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
+ @NonNull private final ShadeInteractor mShadeInteractor;
@Nullable private final TouchProcessor mTouchProcessor;
@NonNull private final SessionTracker mSessionTracker;
@NonNull private final Lazy<DeviceEntryUdfpsTouchOverlayViewModel>
@@ -290,7 +292,8 @@ public class UdfpsController implements DozeReceiver, Dumpable {
mKeyguardTransitionInteractor,
mSelectedUserInteractor,
mDeviceEntryUdfpsTouchOverlayViewModel,
- mDefaultUdfpsTouchOverlayViewModel
+ mDefaultUdfpsTouchOverlayViewModel,
+ mShadeInteractor
)));
}
@@ -656,6 +659,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
@NonNull ActivityLaunchAnimator activityLaunchAnimator,
@NonNull @BiometricsBackground Executor biometricsExecutor,
@NonNull PrimaryBouncerInteractor primaryBouncerInteractor,
+ @NonNull ShadeInteractor shadeInteractor,
@NonNull SinglePointerTouchProcessor singlePointerTouchProcessor,
@NonNull SessionTracker sessionTracker,
@NonNull AlternateBouncerInteractor alternateBouncerInteractor,
@@ -705,6 +709,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
mBiometricExecutor = biometricsExecutor;
mPrimaryBouncerInteractor = primaryBouncerInteractor;
+ mShadeInteractor = shadeInteractor;
mAlternateBouncerInteractor = alternateBouncerInteractor;
mInputManager = inputManager;
mUdfpsKeyguardAccessibilityDelegate = udfpsKeyguardAccessibilityDelegate;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index aabee93fd0a0..b94a1779e10b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -57,6 +57,7 @@ import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.phone.SystemUIDialogManager
@@ -107,6 +108,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor(
private val selectedUserInteractor: SelectedUserInteractor,
private val deviceEntryUdfpsTouchOverlayViewModel: Lazy<DeviceEntryUdfpsTouchOverlayViewModel>,
private val defaultUdfpsTouchOverlayViewModel: Lazy<DefaultUdfpsTouchOverlayViewModel>,
+ private val shadeInteractor: ShadeInteractor,
) {
private var overlayViewLegacy: UdfpsView? = null
private set
@@ -277,7 +279,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor(
updateAccessibilityViewLocation(sensorBounds)
},
statusBarStateController,
- primaryBouncerInteractor,
+ shadeInteractor,
dialogManager,
dumpManager
)
@@ -303,6 +305,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor(
udfpsKeyguardAccessibilityDelegate,
selectedUserInteractor,
transitionInteractor,
+ shadeInteractor,
)
}
REASON_AUTH_BP -> {
@@ -310,7 +313,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor(
UdfpsBpViewController(
view.addUdfpsView(R.layout.udfps_bp_view),
statusBarStateController,
- primaryBouncerInteractor,
+ shadeInteractor,
dialogManager,
dumpManager
)
@@ -320,7 +323,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor(
UdfpsFpmEmptyViewController(
view.addUdfpsView(R.layout.udfps_fpm_empty_view),
statusBarStateController,
- primaryBouncerInteractor,
+ shadeInteractor,
dialogManager,
dumpManager
)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt
index 88002e7b8492..ab3fbb191527 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt
@@ -15,9 +15,9 @@
*/
package com.android.systemui.biometrics
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.phone.SystemUIDialogManager
/**
@@ -28,13 +28,13 @@ import com.android.systemui.statusbar.phone.SystemUIDialogManager
class UdfpsFpmEmptyViewController(
view: UdfpsFpmEmptyView,
statusBarStateController: StatusBarStateController,
- primaryBouncerInteractor: PrimaryBouncerInteractor,
+ shadeInteractor: ShadeInteractor,
systemUIDialogManager: SystemUIDialogManager,
dumpManager: DumpManager
) : UdfpsAnimationViewController<UdfpsFpmEmptyView>(
view,
statusBarStateController,
- primaryBouncerInteractor,
+ shadeInteractor,
systemUIDialogManager,
dumpManager
) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index 64148f6035e0..9f170241269d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -36,6 +36,7 @@ import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
@@ -68,16 +69,17 @@ open class UdfpsKeyguardViewControllerLegacy(
systemUIDialogManager: SystemUIDialogManager,
private val udfpsController: UdfpsController,
private val activityLaunchAnimator: ActivityLaunchAnimator,
- primaryBouncerInteractor: PrimaryBouncerInteractor,
+ private val primaryBouncerInteractor: PrimaryBouncerInteractor,
private val alternateBouncerInteractor: AlternateBouncerInteractor,
private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
private val selectedUserInteractor: SelectedUserInteractor,
private val transitionInteractor: KeyguardTransitionInteractor,
+ shadeInteractor: ShadeInteractor,
) :
UdfpsAnimationViewController<UdfpsKeyguardViewLegacy>(
view,
statusBarStateController,
- primaryBouncerInteractor,
+ shadeInteractor,
systemUIDialogManager,
dumpManager,
) {
@@ -319,7 +321,7 @@ open class UdfpsKeyguardViewControllerLegacy(
}
@VisibleForTesting
- override suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job {
+ suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job {
return scope.launch {
primaryBouncerInteractor.bouncerExpansion.collect { bouncerExpansion: Float ->
inputBouncerExpansion = bouncerExpansion
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
index 8ae6f87f4f83..307b9856cbb5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
@@ -19,6 +19,7 @@ package com.android.systemui.biometrics.dagger
import android.content.res.Resources
import com.android.internal.R
import com.android.systemui.CoreStartable
+import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.EllipseOverlapDetectorParams
import com.android.systemui.biometrics.UdfpsUtils
import com.android.systemui.biometrics.data.repository.BiometricStatusRepository
@@ -38,18 +39,30 @@ import com.android.systemui.biometrics.udfps.EllipseOverlapDetector
import com.android.systemui.biometrics.udfps.OverlapDetector
import com.android.systemui.biometrics.ui.binder.SideFpsOverlayViewBinder
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
import com.android.systemui.util.concurrency.ThreadFactory
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
import java.util.concurrent.Executor
import javax.inject.Qualifier
/** Dagger module for all things biometric. */
@Module
interface BiometricsModule {
+ /** Starts AuthController. */
+ @Binds
+ @IntoMap
+ @ClassKey(AuthController::class)
+ fun bindAuthControllerStartable(service: AuthController): CoreStartable
+
+ /** Listen to config changes for AuthController. */
+ @Binds
+ @IntoSet
+ fun bindAuthControllerConfigChanges(service: AuthController): ConfigurationListener
@Binds
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
index a8c9446fd689..c36e0e21d021 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
@@ -47,6 +47,7 @@ import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
import com.android.systemui.util.kotlin.sample
+import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.combine
@@ -59,49 +60,56 @@ class SideFpsOverlayViewBinder
constructor(
@Application private val applicationScope: CoroutineScope,
@Application private val applicationContext: Context,
- private val biometricStatusInteractor: BiometricStatusInteractor,
- private val displayStateInteractor: DisplayStateInteractor,
- private val deviceEntrySideFpsOverlayInteractor: DeviceEntrySideFpsOverlayInteractor,
- private val fpsUnlockTracker: FpsUnlockTracker,
- private val layoutInflater: LayoutInflater,
- private val sideFpsProgressBarViewModel: SideFpsProgressBarViewModel,
- private val sfpsSensorInteractor: SideFpsSensorInteractor,
- private val windowManager: WindowManager
+ private val biometricStatusInteractor: Lazy<BiometricStatusInteractor>,
+ private val displayStateInteractor: Lazy<DisplayStateInteractor>,
+ private val deviceEntrySideFpsOverlayInteractor: Lazy<DeviceEntrySideFpsOverlayInteractor>,
+ private val fpsUnlockTracker: Lazy<FpsUnlockTracker>,
+ private val layoutInflater: Lazy<LayoutInflater>,
+ private val sideFpsProgressBarViewModel: Lazy<SideFpsProgressBarViewModel>,
+ private val sfpsSensorInteractor: Lazy<SideFpsSensorInteractor>,
+ private val windowManager: Lazy<WindowManager>
) : CoreStartable {
override fun start() {
if (!SideFpsControllerRefactor.isEnabled) {
return
}
+
applicationScope
.launch {
- combine(
- biometricStatusInteractor.sfpsAuthenticationReason,
- deviceEntrySideFpsOverlayInteractor.showIndicatorForDeviceEntry,
- sideFpsProgressBarViewModel.isVisible,
- ::Triple
- )
- .sample(displayStateInteractor.isInRearDisplayMode, ::Pair)
- .collect { (combinedFlows, isInRearDisplayMode: Boolean) ->
- val (
- systemServerAuthReason,
- showIndicatorForDeviceEntry,
- progressBarIsVisible) =
- combinedFlows
- if (!isInRearDisplayMode) {
- if (progressBarIsVisible) {
- hide()
- } else if (systemServerAuthReason != NotRunning) {
- show()
- } else if (showIndicatorForDeviceEntry) {
- show()
- } else {
- hide()
+ sfpsSensorInteractor.get().isAvailable.collect { isSfpsAvailable ->
+ if (isSfpsAvailable) {
+ combine(
+ biometricStatusInteractor.get().sfpsAuthenticationReason,
+ deviceEntrySideFpsOverlayInteractor
+ .get()
+ .showIndicatorForDeviceEntry,
+ sideFpsProgressBarViewModel.get().isVisible,
+ ::Triple
+ )
+ .sample(displayStateInteractor.get().isInRearDisplayMode, ::Pair)
+ .collect { (combinedFlows, isInRearDisplayMode: Boolean) ->
+ val (
+ systemServerAuthReason,
+ showIndicatorForDeviceEntry,
+ progressBarIsVisible) =
+ combinedFlows
+ if (!isInRearDisplayMode) {
+ if (progressBarIsVisible) {
+ hide()
+ } else if (systemServerAuthReason != NotRunning) {
+ show()
+ } else if (showIndicatorForDeviceEntry) {
+ show()
+ } else {
+ hide()
+ }
+ }
}
- }
}
+ }
}
- .invokeOnCompletion { fpsUnlockTracker.stopTracking() }
+ .invokeOnCompletion { fpsUnlockTracker.get().stopTracking() }
}
private var overlayView: View? = null
@@ -113,29 +121,29 @@ constructor(
if (it.isAttachedToWindow) {
lottie = it.requireViewById<LottieAnimationView>(R.id.sidefps_animation)
lottie?.pauseAnimation()
- windowManager.removeView(it)
+ windowManager.get().removeView(it)
}
}
- overlayView = layoutInflater.inflate(R.layout.sidefps_view, null, false)
+ overlayView = layoutInflater.get().inflate(R.layout.sidefps_view, null, false)
val overlayViewModel =
SideFpsOverlayViewModel(
applicationContext,
- biometricStatusInteractor,
- deviceEntrySideFpsOverlayInteractor,
- displayStateInteractor,
- sfpsSensorInteractor,
- sideFpsProgressBarViewModel
+ biometricStatusInteractor.get(),
+ deviceEntrySideFpsOverlayInteractor.get(),
+ displayStateInteractor.get(),
+ sfpsSensorInteractor.get(),
+ sideFpsProgressBarViewModel.get()
)
- bind(overlayView!!, overlayViewModel, fpsUnlockTracker, windowManager)
+ bind(overlayView!!, overlayViewModel, fpsUnlockTracker.get(), windowManager.get())
overlayView!!.visibility = View.INVISIBLE
- windowManager.addView(overlayView, overlayViewModel.defaultOverlayViewParams)
+ windowManager.get().addView(overlayView, overlayViewModel.defaultOverlayViewParams)
}
/** Hide the side fingerprint sensor indicator */
private fun hide() {
if (overlayView != null) {
- windowManager.removeView(overlayView)
+ windowManager.get().removeView(overlayView)
overlayView = null
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
index a12db6f8f346..779446d3cd37 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
@@ -117,10 +117,10 @@ interface CommunalWidgetDao {
fun updateItemRank(itemUid: Long, order: Int)
@Transaction
- fun updateWidgetOrder(ids: List<Int>) {
- ids.forEachIndexed { index, it ->
- val widget = getWidgetByIdNow(it)
- updateItemRank(widget.itemId, ids.size - index)
+ fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {
+ widgetIdToPriorityMap.forEach { (id, priority) ->
+ val widget = getWidgetByIdNow(id)
+ updateItemRank(widget.itemId, priority)
}
}
@@ -129,7 +129,7 @@ interface CommunalWidgetDao {
return insertWidget(
widgetId = widgetId,
componentName = provider.flattenToString(),
- insertItemRank(priority),
+ itemId = insertItemRank(priority),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index ded5581a3034..d1bbe5743a24 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -62,8 +62,12 @@ interface CommunalWidgetRepository {
/** Delete a widget by id from app widget service and the database. */
fun deleteWidget(widgetId: Int) {}
- /** Update the order of widgets in the database. */
- fun updateWidgetOrder(ids: List<Int>) {}
+ /**
+ * Update the order of widgets in the database.
+ *
+ * @param widgetIdToPriorityMap mapping of the widget ids to the priority of the widget.
+ */
+ fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {}
}
@OptIn(ExperimentalCoroutinesApi::class)
@@ -168,11 +172,11 @@ constructor(
}
}
- override fun updateWidgetOrder(ids: List<Int>) {
+ override fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {
applicationScope.launch(bgDispatcher) {
- communalWidgetDao.updateWidgetOrder(ids)
+ communalWidgetDao.updateWidgetOrder(widgetIdToPriorityMap)
logger.i({ "Updated the order of widget list with ids: $str1." }) {
- str1 = ids.toString()
+ str1 = widgetIdToPriorityMap.toString()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index e342c6bca6fa..0f4e583eda45 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -102,8 +102,13 @@ constructor(
/** Delete a widget by id. */
fun deleteWidget(id: Int) = widgetRepository.deleteWidget(id)
- /** Reorder widgets. The order in the list will be their display order in the hub. */
- fun updateWidgetOrder(ids: List<Int>) = widgetRepository.updateWidgetOrder(ids)
+ /**
+ * Reorder the widgets.
+ *
+ * @param widgetIdToPriorityMap mapping of the widget ids to their new priorities.
+ */
+ fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) =
+ widgetRepository.updateWidgetOrder(widgetIdToPriorityMap)
/** A list of widget content to be displayed in the communal hub. */
val widgetContent: Flow<List<CommunalContentModel.Widget>> =
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
index bb9b4b5f522f..3ae522970365 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
@@ -20,6 +20,7 @@ import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetProviderInfo
import android.widget.RemoteViews
import com.android.systemui.communal.shared.model.CommunalContentSize
+import java.util.UUID
/** Encapsulates data for a communal content. */
sealed interface CommunalContentModel {
@@ -39,6 +40,13 @@ sealed interface CommunalContentModel {
override val size = CommunalContentSize.HALF
}
+ /** A placeholder item representing a new widget being added */
+ class WidgetPlaceholder : CommunalContentModel {
+ override val key: String = "widget_placeholder_${UUID.randomUUID()}"
+ // Same as widget size.
+ override val size = CommunalContentSize.HALF
+ }
+
class Tutorial(
id: Int,
override val size: CommunalContentSize,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 708f137017ca..577e40412e4e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal.ui.viewmodel
+import android.content.ComponentName
import android.os.PowerManager
import android.os.SystemClock
import android.view.MotionEvent
@@ -53,6 +54,13 @@ abstract class BaseCommunalViewModel(
communalInteractor.setTransitionState(transitionState)
}
+ /**
+ * Called when a widget is added via drag and drop from the widget picker into the communal hub.
+ */
+ fun onAddWidget(componentName: ComponentName, priority: Int) {
+ communalInteractor.addWidget(componentName, priority)
+ }
+
// TODO(b/308813166): remove once CommunalContainer is moved lower in z-order and doesn't block
// touches anymore.
/** Called when a touch is received outside the edge swipe area when hub mode is closed. */
@@ -82,8 +90,14 @@ abstract class BaseCommunalViewModel(
/** Called as the UI requests deleting a widget. */
open fun onDeleteWidget(id: Int) {}
- /** Called as the UI requests reordering widgets. */
- open fun onReorderWidgets(ids: List<Int>) {}
+ /**
+ * Called as the UI requests reordering widgets.
+ *
+ * @param widgetIdToPriorityMap mapping of the widget ids to its priority. When re-ordering to
+ * add a new item in the middle, provide the priorities of existing widgets as if the new item
+ * existed, and then, call [onAddWidget] to add the new item at intended order.
+ */
+ open fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) {}
/** Called as the UI requests opening the widget editor. */
open fun onOpenWidgetEditor() {}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index c82e00038b34..368df9ea3693 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -47,5 +47,6 @@ constructor(
override fun onDeleteWidget(id: Int) = communalInteractor.deleteWidget(id)
- override fun onReorderWidgets(ids: List<Int>) = communalInteractor.updateWidgetOrder(ids)
+ override fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) =
+ communalInteractor.updateWidgetOrder(widgetIdToPriorityMap)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index 0a13e4887ebe..c936c636f5fc 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -40,6 +40,7 @@ constructor(
private var windowManagerService: IWindowManager? = null,
) : ComponentActivity() {
companion object {
+ private const val EXTRA_IS_PENDING_WIDGET_DRAG = "is_pending_widget_drag"
private const val EXTRA_FILTER_STRATEGY = "filter_strategy"
private const val FILTER_STRATEGY_GLANCEABLE_HUB = 1
private const val TAG = "EditWidgetsActivity"
@@ -49,10 +50,23 @@ constructor(
registerForActivityResult(StartActivityForResult()) { result ->
when (result.resultCode) {
RESULT_OK -> {
- result.data
- ?.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME, ComponentName::class.java)
- ?.let { communalInteractor.addWidget(it, 0) }
- ?: run { Log.w(TAG, "No AppWidgetProviderInfo found in result.") }
+ result.data?.let { intent ->
+ val isPendingWidgetDrag =
+ intent.getBooleanExtra(EXTRA_IS_PENDING_WIDGET_DRAG, false)
+ // Nothing to do when a widget is being dragged & dropped. The drop
+ // target in the communal grid will receive the widget to be added (if
+ // the user drops it over).
+ if (!isPendingWidgetDrag) {
+ intent
+ .getParcelableExtra(
+ Intent.EXTRA_COMPONENT_NAME,
+ ComponentName::class.java
+ )
+ ?.let { communalInteractor.addWidget(it, 0) }
+ ?: run { Log.w(TAG, "No AppWidgetProviderInfo found in result.") }
+ }
+ }
+ ?: run { Log.w(TAG, "No data in result.") }
}
else ->
Log.w(
@@ -65,8 +79,6 @@ constructor(
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- setShowWhenLocked(true)
-
setCommunalEditWidgetActivityContent(
activity = this,
viewModel = communalViewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 236c5b8ed2d7..50f861fad9e4 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -23,6 +23,8 @@ import android.content.Context;
import android.hardware.SensorPrivacyManager;
import com.android.keyguard.KeyguardViewController;
+import com.android.systemui.ScreenDecorationsModule;
+import com.android.systemui.accessibility.SystemActionsModule;
import com.android.systemui.battery.BatterySaverModule;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerImpl;
@@ -34,6 +36,7 @@ import com.android.systemui.plugins.qs.QSFactory;
import com.android.systemui.power.dagger.PowerModule;
import com.android.systemui.qs.dagger.QSModule;
import com.android.systemui.qs.tileimpl.QSFactoryImpl;
+import com.android.systemui.reardisplay.RearDisplayModule;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsImplementation;
import com.android.systemui.rotationlock.RotationLockModule;
@@ -59,6 +62,7 @@ import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyControllerImpl;
import com.android.systemui.statusbar.policy.SensorPrivacyController;
import com.android.systemui.statusbar.policy.SensorPrivacyControllerImpl;
+import com.android.systemui.toast.ToastModule;
import com.android.systemui.volume.dagger.VolumeModule;
import com.android.systemui.wallpapers.dagger.WallpaperModule;
@@ -89,19 +93,23 @@ import javax.inject.Named;
CollapsedStatusBarFragmentStartableModule.class,
GestureModule.class,
HeadsUpModule.class,
+ KeyboardShortcutsModule.class,
MediaModule.class,
MultiUserUtilsModule.class,
NavigationBarControllerModule.class,
PowerModule.class,
QSModule.class,
- ShadeModule.class,
+ RearDisplayModule.class,
ReferenceScreenshotModule.class,
RotationLockModule.class,
- SceneContainerFrameworkModule.class,
+ ScreenDecorationsModule.class,
+ SystemActionsModule.class,
+ ShadeModule.class,
StartCentralSurfacesModule.class,
+ SceneContainerFrameworkModule.class,
+ ToastModule.class,
VolumeModule.class,
- WallpaperModule.class,
- KeyboardShortcutsModule.class
+ WallpaperModule.class
})
public abstract class ReferenceSystemUIModule {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index d041acb4601a..ac71664e5590 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -19,12 +19,9 @@ package com.android.systemui.dagger
import com.android.keyguard.KeyguardBiometricLockoutLogger
import com.android.systemui.CoreStartable
import com.android.systemui.LatencyTester
-import com.android.systemui.ScreenDecorations
import com.android.systemui.SliceBroadcastRelayHandler
-import com.android.systemui.accessibility.SystemActions
import com.android.systemui.accessibility.Magnification
import com.android.systemui.back.domain.interactor.BackActionInteractor
-import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.BiometricNotificationService
import com.android.systemui.clipboardoverlay.ClipboardListener
import com.android.systemui.controls.dagger.StartControlsStartableModule
@@ -46,10 +43,6 @@ import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper
import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
import com.android.systemui.media.taptotransfer.sender.MediaTttSenderCoordinator
import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwitcherCoreStartable
-import com.android.systemui.power.PowerUI
-import com.android.systemui.reardisplay.RearDisplayDialogController
-import com.android.systemui.recents.Recents
-import com.android.systemui.recents.ScreenPinningRequest
import com.android.systemui.settings.dagger.MultiUserUtilsModule
import com.android.systemui.shortcut.ShortcutKeyDispatcher
import com.android.systemui.statusbar.ImmersiveModeConfirmation
@@ -61,11 +54,9 @@ import com.android.systemui.statusbar.phone.StatusBarHeadsUpChangeListener
import com.android.systemui.stylus.StylusUsiPowerStartable
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import com.android.systemui.theme.ThemeOverlayController
-import com.android.systemui.toast.ToastUI
import com.android.systemui.usb.StorageNotification
import com.android.systemui.util.NotificationChannels
import com.android.systemui.util.StartBinderLoggerModule
-import com.android.systemui.volume.VolumeUI
import com.android.systemui.wallpapers.dagger.WallpaperModule
import com.android.systemui.wmshell.WMShell
import dagger.Binds
@@ -74,7 +65,12 @@ import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
/**
- * Collection of {@link CoreStartable}s that should be run on AOSP.
+ * DEPRECATED: DO NOT ADD THINGS TO THIS FILE.
+ *
+ * Add a feature specific daggger module for what you are working on. Bind your CoreStartable there.
+ * Include that module where it is needed.
+ *
+ * @deprecated
*/
@Module(
includes = [
@@ -85,12 +81,6 @@ import dagger.multibindings.IntoMap
]
)
abstract class SystemUICoreStartableModule {
- /** Inject into AuthController. */
- @Binds
- @IntoMap
- @ClassKey(AuthController::class)
- abstract fun bindAuthController(service: AuthController): CoreStartable
-
/** Inject into BiometricNotificationService */
@Binds
@IntoMap
@@ -158,18 +148,6 @@ abstract class SystemUICoreStartableModule {
@PerUser
abstract fun bindNotificationChannels(sysui: NotificationChannels): CoreStartable
- /** Inject into PowerUI. */
- @Binds
- @IntoMap
- @ClassKey(PowerUI::class)
- abstract fun bindPowerUI(sysui: PowerUI): CoreStartable
-
- /** Inject into Recents. */
- @Binds
- @IntoMap
- @ClassKey(Recents::class)
- abstract fun bindRecents(sysui: Recents): CoreStartable
-
/** Inject into ImmersiveModeConfirmation. */
@Binds
@IntoMap
@@ -182,12 +160,6 @@ abstract class SystemUICoreStartableModule {
@ClassKey(RingtonePlayer::class)
abstract fun bind(sysui: RingtonePlayer): CoreStartable
- /** Inject into ScreenDecorations. */
- @Binds
- @IntoMap
- @ClassKey(ScreenDecorations::class)
- abstract fun bindScreenDecorations(sysui: ScreenDecorations): CoreStartable
-
/** Inject into GesturePointerEventHandler. */
@Binds
@IntoMap
@@ -218,23 +190,12 @@ abstract class SystemUICoreStartableModule {
@ClassKey(StorageNotification::class)
abstract fun bindStorageNotification(sysui: StorageNotification): CoreStartable
- /** Inject into SystemActions. */
- @Binds
- @IntoMap
- @ClassKey(SystemActions::class)
- abstract fun bindSystemActions(sysui: SystemActions): CoreStartable
-
/** Inject into ThemeOverlayController. */
@Binds
@IntoMap
@ClassKey(ThemeOverlayController::class)
abstract fun bindThemeOverlayController(sysui: ThemeOverlayController): CoreStartable
- /** Inject into ToastUI. */
- @Binds
- @IntoMap
- @ClassKey(ToastUI::class)
- abstract fun bindToastUI(service: ToastUI): CoreStartable
/** Inject into MediaOutputSwitcherDialogUI. */
@Binds
@@ -242,12 +203,6 @@ abstract class SystemUICoreStartableModule {
@ClassKey(MediaOutputSwitcherDialogUI::class)
abstract fun MediaOutputSwitcherDialogUI(sysui: MediaOutputSwitcherDialogUI): CoreStartable
- /** Inject into VolumeUI. */
- @Binds
- @IntoMap
- @ClassKey(VolumeUI::class)
- abstract fun bindVolumeUI(sysui: VolumeUI): CoreStartable
-
/** Inject into Magnification. */
@Binds
@IntoMap
@@ -293,11 +248,6 @@ abstract class SystemUICoreStartableModule {
abstract fun bindChipbarController(sysui: ChipbarCoordinator): CoreStartable
- /** Inject into RearDisplayDialogController) */
- @Binds
- @IntoMap
- @ClassKey(RearDisplayDialogController::class)
- abstract fun bindRearDisplayDialogController(sysui: RearDisplayDialogController): CoreStartable
/** Inject into StylusUsiPowerStartable) */
@Binds
@@ -361,9 +311,4 @@ abstract class SystemUICoreStartableModule {
@IntoMap
@ClassKey(KeyguardDismissBinder::class)
abstract fun bindKeyguardDismissBinder(impl: KeyguardDismissBinder): CoreStartable
-
- @Binds
- @IntoMap
- @ClassKey(ScreenPinningRequest::class)
- abstract fun bindScreenPinningRequest(impl: ScreenPinningRequest): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index f6db978efadc..1b350055e8de 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -585,10 +585,6 @@ object Flags {
@JvmField
val SPLIT_SHADE_SUBPIXEL_OPTIMIZATION = unreleasedFlag("split_shade_subpixel_optimization")
- // TODO(b/288868056): Tracking Bug
- @JvmField
- val PARTIAL_SCREEN_SHARING_TASK_SWITCHER = unreleasedFlag("pss_task_switcher")
-
// TODO(b/278761837): Tracking Bug
@JvmField val USE_NEW_ACTIVITY_STARTER = releasedFlag(name = "use_new_activity_starter")
@@ -609,11 +605,6 @@ object Flags {
val BIGPICTURE_NOTIFICATION_LAZY_LOADING =
unreleasedFlag("bigpicture_notification_lazy_loading")
- // TODO(b/292062937): Tracking bug
- @JvmField
- val NOTIFICATION_CLEARABLE_REFACTOR =
- unreleasedFlag("notification_clearable_refactor")
-
// TODO(b/283740863): Tracking Bug
@JvmField
val ENABLE_NEW_PRIVACY_DIALOG = releasedFlag("enable_new_privacy_dialog")
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt
index 629b361064a7..cfa5294567b7 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt
@@ -65,4 +65,11 @@ class SeekableSliderEventProducer : SliderEventProducer, OnSeekBarChangeListener
SliderEvent(SliderEventType.STOPPED_TRACKING_TOUCH, previousEvent.currentProgress)
}
}
+
+ /** The arrow navigation that was operating the slider has stopped. */
+ fun onArrowUp() {
+ _currentEvent.update { previousEvent ->
+ SliderEvent(SliderEventType.ARROW_UP, previousEvent.currentProgress)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
index d89cf63cd483..10098faaa05e 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
@@ -58,7 +58,7 @@ class SeekableSliderTracker(
override suspend fun iterateState(event: SliderEvent) {
when (currentState) {
- SliderState.IDLE -> handleIdle(event.type)
+ SliderState.IDLE -> handleIdle(event.type, event.currentProgress)
SliderState.WAIT -> handleWait(event.type, event.currentProgress)
SliderState.DRAG_HANDLE_ACQUIRED_BY_TOUCH -> handleAcquired(event.type)
SliderState.DRAG_HANDLE_DRAGGING -> handleDragging(event.type, event.currentProgress)
@@ -67,17 +67,26 @@ class SeekableSliderTracker(
SliderState.DRAG_HANDLE_RELEASED_FROM_TOUCH -> setState(SliderState.IDLE)
SliderState.JUMP_TRACK_LOCATION_SELECTED -> handleJumpToTrack(event.type)
SliderState.JUMP_BOOKEND_SELECTED -> handleJumpToBookend(event.type)
+ SliderState.ARROW_HANDLE_MOVED_ONCE -> handleArrowOnce(event.type)
+ SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY ->
+ handleArrowContinuous(event.type, event.currentProgress)
+ SliderState.ARROW_HANDLE_REACHED_BOOKEND -> handleArrowBookend()
}
latestProgress = event.currentProgress
}
- private fun handleIdle(newEventType: SliderEventType) {
+ private fun handleIdle(newEventType: SliderEventType, currentProgress: Float) {
if (newEventType == SliderEventType.STARTED_TRACKING_TOUCH) {
timerJob = launchTimer()
// The WAIT state will wait for the timer to complete or a slider progress to occur.
// This will disambiguate between an imprecise touch that acquires the slider handle,
// and a select and jump operation in the slider track.
setState(SliderState.WAIT)
+ } else if (newEventType == SliderEventType.PROGRESS_CHANGE_BY_PROGRAM) {
+ val state =
+ if (bookendReached(currentProgress)) SliderState.ARROW_HANDLE_REACHED_BOOKEND
+ else SliderState.ARROW_HANDLE_MOVED_ONCE
+ setState(state)
}
}
@@ -176,6 +185,13 @@ class SeekableSliderTracker(
SliderState.DRAG_HANDLE_REACHED_BOOKEND -> executeOnBookend()
SliderState.JUMP_TRACK_LOCATION_SELECTED ->
sliderListener.onProgressJump(latestProgress)
+ SliderState.ARROW_HANDLE_MOVED_ONCE -> sliderListener.onSelectAndArrow(latestProgress)
+ SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY -> sliderListener.onProgress(latestProgress)
+ SliderState.ARROW_HANDLE_REACHED_BOOKEND -> {
+ executeOnBookend()
+ // This transitory execution must also reset the state
+ resetState()
+ }
else -> {}
}
}
@@ -204,6 +220,43 @@ class SeekableSliderTracker(
currentProgress <= config.lowerBookendThreshold
}
+ private fun handleArrowOnce(newEventType: SliderEventType) {
+ val nextState =
+ when (newEventType) {
+ SliderEventType.STARTED_TRACKING_TOUCH -> {
+ // Launching the timer and going to WAIT
+ timerJob = launchTimer()
+ SliderState.WAIT
+ }
+ SliderEventType.PROGRESS_CHANGE_BY_PROGRAM ->
+ SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY
+ SliderEventType.ARROW_UP -> SliderState.IDLE
+ else -> SliderState.ARROW_HANDLE_MOVED_ONCE
+ }
+ setState(nextState)
+ }
+
+ private fun handleArrowContinuous(newEventType: SliderEventType, currentProgress: Float) {
+ val reachedBookend = bookendReached(currentProgress)
+ val nextState =
+ when (newEventType) {
+ SliderEventType.ARROW_UP -> SliderState.IDLE
+ SliderEventType.STARTED_TRACKING_TOUCH -> {
+ // Launching the timer and going to WAIT
+ timerJob = launchTimer()
+ SliderState.WAIT
+ }
+ SliderEventType.PROGRESS_CHANGE_BY_PROGRAM -> {
+ if (reachedBookend) SliderState.ARROW_HANDLE_REACHED_BOOKEND
+ else SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY
+ }
+ else -> SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY
+ }
+ setState(nextState)
+ }
+
+ private fun handleArrowBookend() = setState(SliderState.IDLE)
+
@VisibleForTesting
fun setState(state: SliderState) {
currentState = state
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt
index 413e27763ba8..4a63941b3f8c 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt
@@ -29,5 +29,5 @@ enum class SliderEventType {
/* The slider has stopped tracking touch events. */
STOPPED_TRACKING_TOUCH,
/* The external (not touch) stimulus that was modifying the slider progress has stopped. */
- EXTERNAL_STIMULUS_RELEASE,
+ ARROW_UP,
}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderState.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderState.kt
index fe092e67036b..de6ddd7168e5 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderState.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderState.kt
@@ -32,6 +32,12 @@ enum class SliderState {
DRAG_HANDLE_REACHED_BOOKEND,
/* A location in the slider track has been selected. */
JUMP_TRACK_LOCATION_SELECTED,
- /* The slider handled moved to a bookend after it was selected. */
+ /* The slider handle moved to a bookend after it was selected. */
JUMP_BOOKEND_SELECTED,
+ /** The slider handle moved due to single select-and-arrow operation */
+ ARROW_HANDLE_MOVED_ONCE,
+ /** The slider handle moves continuously due to constant select-and-arrow operations */
+ ARROW_HANDLE_MOVES_CONTINUOUSLY,
+ /** The slider handle reached a bookend due to a select-and-arrow operation */
+ ARROW_HANDLE_REACHED_BOOKEND,
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
index c490ce7db843..342325ffee91 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
@@ -30,7 +30,7 @@ import android.net.Uri
import android.os.Binder
import android.os.Bundle
import android.util.Log
-import com.android.app.tracing.TraceUtils.Companion.runBlocking
+import com.android.app.tracing.coroutines.runBlocking
import com.android.systemui.SystemUIAppComponentFactoryBase
import com.android.systemui.SystemUIAppComponentFactoryBase.ContextAvailableCallback
import com.android.systemui.dagger.qualifiers.Main
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index 4d60dd0bea62..17d78365417b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -626,17 +626,19 @@ constructor(
faceAuthLogger.skippingDetection(_isAuthRunning.value, detectCancellationSignal != null)
return
}
- detectCancellationSignal?.cancel()
- detectCancellationSignal = CancellationSignal()
withContext(mainDispatcher) {
// We always want to invoke face detect in the main thread.
faceAuthLogger.faceDetectionStarted()
- faceManager?.detectFace(
- checkNotNull(detectCancellationSignal),
- detectionCallback,
- SysUiFaceAuthenticateOptions(currentUserId, uiEvent, uiEvent.extraInfo)
- .toFaceAuthenticateOptions()
- )
+ detectCancellationSignal?.cancel()
+ detectCancellationSignal = CancellationSignal()
+ detectCancellationSignal?.let {
+ faceManager?.detectFace(
+ it,
+ detectionCallback,
+ SysUiFaceAuthenticateOptions(currentUserId, uiEvent, uiEvent.extraInfo)
+ .toFaceAuthenticateOptions()
+ )
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
index c98f63717938..ecf78d550a3f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
@@ -23,15 +23,18 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
+import com.android.systemui.keyguard.data.repository.BiometricType
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.res.R
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.launch
/**
* Encapsulates business logic for device entry events that impact the side fingerprint sensor
@@ -41,6 +44,7 @@ import kotlinx.coroutines.flow.merge
class DeviceEntrySideFpsOverlayInteractor
@Inject
constructor(
+ @Application private val applicationScope: CoroutineScope,
@Application private val context: Context,
deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
private val primaryBouncerInteractor: PrimaryBouncerInteractor,
@@ -50,7 +54,13 @@ constructor(
init {
if (!DeviceEntryUdfpsRefactor.isEnabled) {
- alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, TAG)
+ applicationScope.launch {
+ deviceEntryFingerprintAuthRepository.availableFpSensorType.collect { sensorType ->
+ if (sensorType == BiometricType.SIDE_FINGERPRINT) {
+ alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, TAG)
+ }
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 9fe5c3f53d96..cecc6537e16e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -18,7 +18,7 @@ package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
import com.android.app.animation.Interpolators
-import com.android.app.tracing.TraceUtils.Companion.launch
+import com.android.app.tracing.coroutines.launch
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlags
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 7882a9758105..388834597f60 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -22,7 +22,7 @@ import android.app.admin.DevicePolicyManager
import android.content.Context
import android.content.Intent
import android.util.Log
-import com.android.app.tracing.TraceUtils.Companion.withContext
+import com.android.app.tracing.coroutines.withContext
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.animation.Expandable
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
index 64ff3b0c238a..12775854c737 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
@@ -32,11 +32,8 @@ import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharedFlow
-import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.shareIn
/**
* Assists in creating sub-flows for a KeyguardTransition. Call [setup] once for a transition, and
@@ -68,8 +65,6 @@ constructor(
* in the range of [0, 1]. View animations should begin and end within a subset of this
* range. This function maps the [startTime] and [duration] into [0, 1], when this subset is
* valid.
- *
- * Will produce a [SharedFlow], so that identical animations can use the same value.
*/
fun sharedFlow(
duration: Duration,
@@ -80,7 +75,7 @@ constructor(
onFinish: (() -> Float)? = null,
interpolator: Interpolator = LINEAR,
name: String? = null
- ): SharedFlow<Float> {
+ ): Flow<Float> {
if (!duration.isPositive()) {
throw IllegalArgumentException("duration must be a positive number: $duration")
}
@@ -137,7 +132,6 @@ constructor(
value
}
.filterNotNull()
- .shareIn(scope, SharingStarted.WhileSubscribed())
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index eee5206498e4..96e83b0ca0f8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -241,7 +241,6 @@ object KeyguardBottomAreaViewBinder {
vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Activated)
settingsMenu.setOnTouchListener(
KeyguardSettingsButtonOnTouchListener(
- view = settingsMenu,
viewModel = viewModel.settingsMenuViewModel,
)
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 362e7e6d4770..fad0370a85d7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -255,6 +255,7 @@ object KeyguardRootViewBinder {
vibratorHelper.performHapticFeedback(
view,
HapticFeedbackConstants.CONFIRM,
+ HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
)
}
}
@@ -264,6 +265,7 @@ object KeyguardRootViewBinder {
vibratorHelper.performHapticFeedback(
view,
HapticFeedbackConstants.REJECT,
+ HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt
index c54203c97013..c6dfcb00a809 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt
@@ -20,12 +20,10 @@ import android.graphics.PointF
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
-import com.android.systemui.animation.view.LaunchableLinearLayout
import com.android.systemui.common.ui.view.rawDistanceFrom
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel
class KeyguardSettingsButtonOnTouchListener(
- private val view: LaunchableLinearLayout,
private val viewModel: KeyguardSettingsMenuViewModel,
) : View.OnTouchListener {
@@ -41,8 +39,10 @@ class KeyguardSettingsButtonOnTouchListener(
MotionEvent.ACTION_UP -> {
view.isPressed = false
val distanceMoved =
- motionEvent
- .rawDistanceFrom(downPositionDisplayCoords.x, downPositionDisplayCoords.y)
+ motionEvent.rawDistanceFrom(
+ downPositionDisplayCoords.x,
+ downPositionDisplayCoords.y
+ )
val isClick = distanceMoved < ViewConfiguration.getTouchSlop()
viewModel.onTouchGestureEnded(isClick)
if (isClick) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
index 11e63e76c289..f67cb684b7a6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
@@ -23,7 +23,6 @@ import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.animation.view.LaunchableLinearLayout
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.common.ui.binder.TextViewBinder
import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
@@ -43,15 +42,13 @@ import kotlinx.coroutines.launch
object KeyguardSettingsViewBinder {
fun bind(
- parentView: View,
+ view: View,
viewModel: KeyguardSettingsMenuViewModel,
longPressViewModel: KeyguardLongPressViewModel,
- rootViewModel: KeyguardRootViewModel,
+ rootViewModel: KeyguardRootViewModel?,
vibratorHelper: VibratorHelper,
activityStarter: ActivityStarter
): DisposableHandle {
- val view = parentView.requireViewById<LaunchableLinearLayout>(R.id.keyguard_settings_button)
-
val disposableHandle =
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
@@ -62,7 +59,6 @@ object KeyguardSettingsViewBinder {
vibratorHelper.vibrate(KeyguardBottomAreaVibrations.Activated)
view.setOnTouchListener(
KeyguardSettingsButtonOnTouchListener(
- view = view,
viewModel = viewModel,
)
)
@@ -96,7 +92,7 @@ object KeyguardSettingsViewBinder {
}
launch {
- rootViewModel.lastRootViewTapPosition.filterNotNull().collect { point ->
+ rootViewModel?.lastRootViewTapPosition?.filterNotNull()?.collect { point ->
if (view.isVisible) {
val hitRect = Rect()
view.getHitRect(hitRect)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
index 24240dfe7402..940d1e10651a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
@@ -142,6 +142,11 @@ class PreviewLifecycleObserver(
return true
}
+ if (renderer == null || onDestroy == null) {
+ Log.wtf(TAG, "Renderer/onDestroy should not be null.")
+ return true
+ }
+
when (message.what) {
KeyguardPreviewConstants.MESSAGE_ID_SLOT_SELECTED -> {
message.data.getString(KeyguardPreviewConstants.KEY_SLOT_ID)?.let { slotId ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
index 16539db648bc..5344696b92fe 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
@@ -33,6 +33,7 @@ import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsMod
import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeClockSection
import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
+import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeMediaSection
import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeNotificationStackScrollLayoutSection
import com.android.systemui.util.kotlin.getOrNull
import java.util.Optional
@@ -63,6 +64,7 @@ constructor(
communalTutorialIndicatorSection: CommunalTutorialIndicatorSection,
smartspaceSection: SmartspaceSection,
clockSection: SplitShadeClockSection,
+ mediaSection: SplitShadeMediaSection,
) : KeyguardBlueprint {
override val id: String = ID
@@ -81,6 +83,7 @@ constructor(
aodBurnInSection,
communalTutorialIndicatorSection,
clockSection,
+ mediaSection,
defaultDeviceEntrySection, // Add LAST: Intentionally has z-order above other views.
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
new file mode 100644
index 000000000000..5afdbaa47d95
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.keyguard.ui.view.layout.sections
+
+import android.content.Context
+import android.view.View
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.widget.FrameLayout
+import androidx.constraintlayout.widget.Barrier
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.END
+import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.TOP
+import com.android.systemui.Flags.migrateClocksToBlueprint
+import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
+import com.android.systemui.media.controls.ui.KeyguardMediaController
+import com.android.systemui.res.R
+import com.android.systemui.shade.NotificationPanelView
+import javax.inject.Inject
+
+/** Aligns media on left side for split shade, below smartspace, date, and weather. */
+class SplitShadeMediaSection
+@Inject
+constructor(
+ private val context: Context,
+ private val notificationPanelView: NotificationPanelView,
+ private val keyguardSmartspaceViewModel: KeyguardSmartspaceViewModel,
+ private val keyguardMediaController: KeyguardMediaController
+) : KeyguardSection() {
+ private val mediaContainerId = R.id.status_view_media_container
+ private val smartSpaceBarrier = R.id.smart_space_barrier_bottom
+
+ override fun addViews(constraintLayout: ConstraintLayout) {
+ if (!migrateClocksToBlueprint()) {
+ return
+ }
+
+ notificationPanelView.findViewById<View>(mediaContainerId)?.let {
+ notificationPanelView.removeView(it)
+ }
+
+ val mediaFrame =
+ FrameLayout(context, null).apply {
+ id = mediaContainerId
+ val padding = context.resources.getDimensionPixelSize(R.dimen.qs_media_padding)
+ val horizontalPadding =
+ padding +
+ context.resources.getDimensionPixelSize(
+ R.dimen.status_view_margin_horizontal
+ )
+
+ setPaddingRelative(horizontalPadding, padding, horizontalPadding, padding)
+ }
+ constraintLayout.addView(mediaFrame)
+ keyguardMediaController.attachSplitShadeContainer(mediaFrame)
+ }
+
+ override fun bindData(constraintLayout: ConstraintLayout) {}
+
+ override fun applyConstraints(constraintSet: ConstraintSet) {
+ if (!migrateClocksToBlueprint()) {
+ return
+ }
+
+ constraintSet.apply {
+ constrainWidth(mediaContainerId, MATCH_CONSTRAINT)
+ constrainHeight(mediaContainerId, WRAP_CONTENT)
+
+ createBarrier(
+ smartSpaceBarrier,
+ Barrier.BOTTOM,
+ 0,
+ *intArrayOf(
+ keyguardSmartspaceViewModel.smartspaceViewId,
+ keyguardSmartspaceViewModel.dateId,
+ keyguardSmartspaceViewModel.weatherId,
+ )
+ )
+ connect(mediaContainerId, TOP, smartSpaceBarrier, BOTTOM)
+ connect(mediaContainerId, START, PARENT_ID, START)
+ connect(mediaContainerId, END, R.id.split_shade_guideline, END)
+ }
+ }
+
+ override fun removeViews(constraintLayout: ConstraintLayout) {
+ if (!migrateClocksToBlueprint()) {
+ return
+ }
+
+ constraintLayout.removeView(mediaContainerId)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
index f4ae365b2613..fa4de044623d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
@@ -20,12 +20,12 @@ import android.content.Context
import android.hardware.biometrics.SensorLocationInternal
import com.android.settingslib.Utils
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
import com.android.systemui.res.R
import javax.inject.Inject
-import kotlin.math.roundToInt
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -45,6 +45,7 @@ constructor(
deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
deviceEntryBackgroundViewModel: DeviceEntryBackgroundViewModel,
fingerprintPropertyRepository: FingerprintPropertyRepository,
+ udfpsOverlayInteractor: UdfpsOverlayInteractor,
) {
private val isSupported: Flow<Boolean> = deviceEntryUdfpsInteractor.isUdfpsSupported
val iconLocation: Flow<IconLocation> =
@@ -73,11 +74,7 @@ constructor(
.onStart {
emit(Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary))
}
- private val fgIconPadding: Flow<Int> =
- configurationInteractor.scaleForResolution.map { scale ->
- (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
- .roundToInt()
- }
+ private val fgIconPadding: Flow<Int> = udfpsOverlayInteractor.iconPadding
val fgViewModel: Flow<DeviceEntryForegroundViewModel.ForegroundIconViewModel> =
combine(
fgIconColor,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index d57e569ca7c8..36bbe4e49415 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -33,6 +33,7 @@ class LockscreenContentViewModel
constructor(
private val interactor: KeyguardBlueprintInteractor,
private val authController: AuthController,
+ val longPress: KeyguardLongPressViewModel,
) {
val isUdfpsVisible: Boolean
get() = authController.isUdfpsSupported
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
index 724241d8d41f..185a78369a9e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
@@ -17,6 +17,7 @@
package com.android.systemui.media.controls.pipeline
import android.content.Context
+import android.content.pm.UserInfo
import android.os.SystemProperties
import android.util.Log
import com.android.internal.annotations.KeepForWeakReference
@@ -88,7 +89,11 @@ constructor(
private val userTrackerCallback =
object : UserTracker.Callback {
override fun onUserChanged(newUser: Int, userContext: Context) {
- handleUserSwitched(newUser)
+ handleUserSwitched()
+ }
+
+ override fun onProfilesChanged(profiles: List<UserInfo>) {
+ handleProfileChanged()
}
}
@@ -109,7 +114,10 @@ constructor(
}
allEntries.put(key, data)
- if (!lockscreenUserManager.isCurrentProfile(data.userId)) {
+ if (
+ !lockscreenUserManager.isCurrentProfile(data.userId) ||
+ !lockscreenUserManager.isProfileAvailable(data.userId)
+ ) {
return
}
@@ -231,7 +239,20 @@ constructor(
}
@VisibleForTesting
- internal fun handleUserSwitched(id: Int) {
+ internal fun handleProfileChanged() {
+ // TODO(b/317221348) re-add media removed when profile is available.
+ allEntries.forEach { (key, data) ->
+ if (!lockscreenUserManager.isProfileAvailable(data.userId)) {
+ // Only remove media when the profile is unavailable.
+ if (DEBUG) Log.d(TAG, "Removing $key after profile change")
+ userEntries.remove(key, data)
+ listeners.forEach { listener -> listener.onMediaDataRemoved(key) }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ internal fun handleUserSwitched() {
// If the user changes, remove all current MediaData objects and inform listeners
val listenersCopy = listeners
val keyCopy = userEntries.keys.toMutableList()
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt
index 945bf9a5c0b2..e15e03822610 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt
@@ -27,6 +27,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.annotation.VisibleForTesting
import com.android.systemui.Dumpable
+import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
@@ -180,7 +181,11 @@ constructor(
/** Called whenever the media hosts visibility changes */
private fun onMediaHostVisibilityChanged(visible: Boolean) {
refreshMediaPosition(reason = "onMediaHostVisibilityChanged")
+
if (visible) {
+ if (migrateClocksToBlueprint() && useSplitShade) {
+ return
+ }
mediaHost.hostView.layoutParams.apply {
height = ViewGroup.LayoutParams.WRAP_CONTENT
width = ViewGroup.LayoutParams.MATCH_PARENT
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
index e827a1ec099d..3e6d46c00df9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
@@ -25,12 +25,12 @@ import com.android.internal.logging.UiEventLogger
import com.android.internal.statusbar.IUndoMediaTransferCallback
import com.android.systemui.CoreStartable
import com.android.systemui.Dumpable
-import com.android.systemui.res.R
import com.android.systemui.common.shared.model.Text
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.media.taptotransfer.MediaTttFlags
import com.android.systemui.media.taptotransfer.common.MediaTttUtils
+import com.android.systemui.res.R
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
import com.android.systemui.temporarydisplay.ViewPriority
@@ -162,7 +162,7 @@ constructor(
logger: MediaTttSenderLogger,
instanceId: InstanceId,
): ChipbarInfo {
- val packageName = checkNotNull(routeInfo.clientPackageName)
+ val packageName = routeInfo.clientPackageName
val otherDeviceName =
if (routeInfo.name.isBlank()) {
context.getString(R.string.media_ttt_default_device_type)
@@ -171,7 +171,7 @@ constructor(
}
val icon =
MediaTttUtils.getIconInfoFromPackageName(context, packageName, isReceiver = false) {
- logger.logPackageNotFound(packageName)
+ packageName?.let { logger.logPackageNotFound(it) }
}
val timeout =
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt
index 3c501277ab8c..2408af192463 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartable.kt
@@ -17,23 +17,22 @@
package com.android.systemui.mediaprojection.taskswitcher
import com.android.systemui.CoreStartable
+import com.android.systemui.Flags.pssTaskSwitcher
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.mediaprojection.taskswitcher.ui.TaskSwitcherNotificationCoordinator
+import dagger.Lazy
import javax.inject.Inject
@SysUISingleton
class MediaProjectionTaskSwitcherCoreStartable
@Inject
constructor(
- private val notificationCoordinator: TaskSwitcherNotificationCoordinator,
- private val featureFlags: FeatureFlags,
+ private val notificationCoordinatorLazy: Lazy<TaskSwitcherNotificationCoordinator>,
) : CoreStartable {
override fun start() {
- if (featureFlags.isEnabled(Flags.PARTIAL_SCREEN_SHARING_TASK_SWITCHER)) {
- notificationCoordinator.start()
+ if (pssTaskSwitcher()) {
+ notificationCoordinatorLazy.get().start()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index 270bfbe4274d..3aa9daac4866 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -37,7 +37,7 @@ import android.os.UserManager
import android.provider.Settings
import android.widget.Toast
import androidx.annotation.VisibleForTesting
-import com.android.app.tracing.TraceUtils.Companion.launch
+import com.android.app.tracing.coroutines.launch
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 1534653bc1dd..958ace358816 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -48,12 +48,13 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.fuelgauge.Estimate;
import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.CoreStartable;
-import com.android.systemui.res.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import java.io.PrintWriter;
import java.util.Arrays;
@@ -62,7 +63,10 @@ import java.util.concurrent.Future;
import javax.inject.Inject;
@SysUISingleton
-public class PowerUI implements CoreStartable, CommandQueue.Callbacks {
+public class PowerUI implements
+ CoreStartable,
+ ConfigurationController.ConfigurationListener,
+ CommandQueue.Callbacks {
static final String TAG = "PowerUI";
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -223,7 +227,7 @@ public class PowerUI implements CoreStartable, CommandQueue.Callbacks {
}
@Override
- public void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigChanged(Configuration newConfig) {
final int mask = ActivityInfo.CONFIG_MCC | ActivityInfo.CONFIG_MNC;
// Safe to modify mLastConfiguration here as it's only updated by the main thread (here).
diff --git a/packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java b/packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java
index 7184fa0685af..8dd0ea0aa2af 100644
--- a/packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java
+++ b/packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java
@@ -16,14 +16,19 @@
package com.android.systemui.power.dagger;
+import com.android.systemui.CoreStartable;
import com.android.systemui.power.EnhancedEstimates;
import com.android.systemui.power.EnhancedEstimatesImpl;
import com.android.systemui.power.PowerNotificationWarnings;
import com.android.systemui.power.PowerUI;
import com.android.systemui.power.data.repository.PowerRepositoryModule;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import dagger.Binds;
import dagger.Module;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
+import dagger.multibindings.IntoSet;
/** Dagger Module for code in the power package. */
@@ -33,6 +38,17 @@ import dagger.Module;
}
)
public interface PowerModule {
+ /** Starts PowerUI. */
+ @Binds
+ @IntoMap
+ @ClassKey(PowerUI.class)
+ CoreStartable bindPowerUIStartable(PowerUI impl);
+
+ /** Listen to config changes for PowerUI. */
+ @Binds
+ @IntoSet
+ ConfigurationController.ConfigurationListener bindPowerUIConfigChanges(PowerUI impl);
+
/** */
@Binds
EnhancedEstimates bindEnhancedEstimates(EnhancedEstimatesImpl enhancedEstimates);
diff --git a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java
index 4b21e447ebf5..f07162377358 100644
--- a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java
@@ -29,11 +29,12 @@ import android.widget.LinearLayout;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.CoreStartable;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.airbnb.lottie.LottieAnimationView;
import com.airbnb.lottie.LottieDrawable;
@@ -57,7 +58,10 @@ import javax.inject.Inject;
*/
@SuppressLint("VisibleForTests") // TODO(b/260264542) Migrate away from DeviceStateManagerGlobal
@SysUISingleton
-public class RearDisplayDialogController implements CoreStartable, CommandQueue.Callbacks {
+public class RearDisplayDialogController implements
+ CoreStartable,
+ ConfigurationController.ConfigurationListener,
+ CommandQueue.Callbacks {
private int[] mFoldedStates;
private boolean mStartedFolded;
@@ -96,7 +100,7 @@ public class RearDisplayDialogController implements CoreStartable, CommandQueue.
}
@Override
- public void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigChanged(Configuration newConfig) {
if (mRearDisplayEducationDialog != null && mRearDisplayEducationDialog.isShowing()
&& mDialogViewContainer != null) {
// Refresh the dialog view when configuration is changed.
diff --git a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayModule.kt b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayModule.kt
new file mode 100644
index 000000000000..6ab294dd9818
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayModule.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.reardisplay
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
+
+@Module
+interface RearDisplayModule {
+
+ /** Start RearDisplayDialogController. */
+ @Binds
+ @IntoMap
+ @ClassKey(RearDisplayDialogController::class)
+ abstract fun bindRearDisplayDialogControllerStartable(
+ impl: RearDisplayDialogController
+ ): CoreStartable
+
+ /** Listen to config changes for RearDisplayDialogController. */
+ @Binds
+ @IntoSet
+ fun bindRearDisplayDialogControllerConfigChanges(
+ impl: RearDisplayDialogController
+ ): ConfigurationListener
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index b041f957d771..4ee65b90fb67 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -23,13 +23,17 @@ import android.provider.Settings;
import com.android.systemui.CoreStartable;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import java.io.PrintWriter;
/**
* A proxy to a Recents implementation.
*/
-public class Recents implements CoreStartable, CommandQueue.Callbacks {
+public class Recents implements
+ CoreStartable,
+ ConfigurationController.ConfigurationListener,
+ CommandQueue.Callbacks {
private final Context mContext;
private final RecentsImplementation mImpl;
@@ -53,7 +57,7 @@ public class Recents implements CoreStartable, CommandQueue.Callbacks {
}
@Override
- public void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigChanged(Configuration newConfig) {
mImpl.onConfigurationChanged(newConfig);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsModule.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsModule.java
index 77a4b9f72cd8..11089173a708 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsModule.java
@@ -18,14 +18,17 @@ package com.android.systemui.recents;
import android.content.Context;
-import com.android.systemui.res.R;
+import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.ContextComponentHelper;
+import com.android.systemui.res.R;
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.ClassKey;
import dagger.multibindings.IntoMap;
+import dagger.multibindings.IntoSet;
/**
* Dagger injection module for {@link RecentsImplementation}
@@ -33,6 +36,28 @@ import dagger.multibindings.IntoMap;
@Module
public abstract class RecentsModule {
+ /** Start Recents. */
+ @Binds
+ @IntoMap
+ @ClassKey(Recents.class)
+ abstract CoreStartable bindRecentsStartable(Recents impl);
+
+ /** Listen to config changes for Recents. */
+ @Binds
+ @IntoSet
+ abstract ConfigurationListener bindRecentsConfigChanges(Recents impl);
+
+ /** Start ScreenPinningRequest. */
+ @Binds
+ @IntoMap
+ @ClassKey(ScreenPinningRequest.class)
+ abstract CoreStartable bindScreenPinningRequestStartable(ScreenPinningRequest impl);
+
+ /** Listen to config changes for ScreenPinningRequest. */
+ @Binds
+ @IntoSet
+ abstract ConfigurationListener bindScreenPinningRequestConfigChanges(ScreenPinningRequest impl);
+
/**
* @return The {@link RecentsImplementation} from the config.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index 3e574e788494..2b717cb32533 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -54,25 +54,29 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import com.android.systemui.CoreStartable;
-import com.android.systemui.res.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.leak.RotationUtils;
+import dagger.Lazy;
+
import java.util.ArrayList;
import javax.inject.Inject;
-import dagger.Lazy;
-
@SysUISingleton
-public class ScreenPinningRequest implements View.OnClickListener,
- NavigationModeController.ModeChangedListener, CoreStartable {
+public class ScreenPinningRequest implements
+ View.OnClickListener,
+ NavigationModeController.ModeChangedListener,
+ CoreStartable,
+ ConfigurationController.ConfigurationListener {
private static final String TAG = "ScreenPinningRequest";
private final Context mContext;
@@ -149,7 +153,7 @@ public class ScreenPinningRequest implements View.OnClickListener,
}
@Override
- public void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigChanged(Configuration newConfig) {
if (mRequestWindow != null) {
mRequestWindow.onConfigurationChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
index a950539e84aa..bee315261f89 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
@@ -29,7 +29,7 @@ import android.view.RemoteAnimationAdapter
import android.view.RemoteAnimationTarget
import android.view.WindowManager
import android.view.WindowManagerGlobal
-import com.android.app.tracing.TraceUtils.Companion.launch
+import com.android.app.tracing.coroutines.launch
import com.android.internal.infra.ServiceConnector
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
index f56f41635006..3081f89d0cb9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
@@ -20,7 +20,7 @@ import android.util.Log
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.app.tracing.TraceUtils.Companion.launch
+import com.android.app.tracing.coroutines.launch
import kotlinx.coroutines.CoroutineScope
import java.util.function.Consumer
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
index 713ede69edec..86f652389b42 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
@@ -25,7 +25,7 @@ import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.shade.ShadeExpansionStateManager
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
-import com.android.app.tracing.TraceUtils.Companion.launch
+import com.android.app.tracing.coroutines.launch
import kotlinx.coroutines.withContext
/** Provides state from the main SystemUI process on behalf of the Screenshot process. */
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
index 38d00f707b6d..238a552604ca 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
@@ -18,7 +18,7 @@ package com.android.systemui.screenshot
import android.media.MediaPlayer
import android.util.Log
-import com.android.app.tracing.TraceUtils.Companion.async
+import com.android.app.tracing.coroutines.async
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.google.errorprone.annotations.CanIgnoreReturnValue
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index f6c25e0a0f2c..e464fd0c8e8e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -5,7 +5,7 @@ import android.os.Trace
import android.util.Log
import android.view.Display
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
-import com.android.app.tracing.TraceUtils.Companion.launch
+import com.android.app.tracing.coroutines.launch
import com.android.internal.logging.UiEventLogger
import com.android.internal.util.ScreenshotRequest
import com.android.systemui.dagger.SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
index 9f416bbf2c59..f2fa0ef3f30f 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
@@ -135,6 +135,10 @@ open class UserTrackerImpl internal constructor(
val filter = IntentFilter().apply {
addAction(Intent.ACTION_LOCALE_CHANGED)
addAction(Intent.ACTION_USER_INFO_CHANGED)
+ addAction(Intent.ACTION_PROFILE_ADDED)
+ addAction(Intent.ACTION_PROFILE_REMOVED)
+ addAction(Intent.ACTION_PROFILE_AVAILABLE)
+ addAction(Intent.ACTION_PROFILE_UNAVAILABLE)
// These get called when a managed profile goes in or out of quiet mode.
addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)
@@ -157,7 +161,11 @@ open class UserTrackerImpl internal constructor(
Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
Intent.ACTION_MANAGED_PROFILE_ADDED,
Intent.ACTION_MANAGED_PROFILE_REMOVED,
- Intent.ACTION_MANAGED_PROFILE_UNLOCKED -> {
+ Intent.ACTION_MANAGED_PROFILE_UNLOCKED,
+ Intent.ACTION_PROFILE_ADDED,
+ Intent.ACTION_PROFILE_REMOVED,
+ Intent.ACTION_PROFILE_AVAILABLE,
+ Intent.ACTION_PROFILE_UNAVAILABLE -> {
handleProfilesChanged()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index d13edf01cc4a..d382b7ae2bf0 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -21,6 +21,8 @@ import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static android.view.WindowManagerPolicyConstants.EXTRA_FROM_BRIGHTNESS_KEY;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
+
import android.app.Activity;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -87,6 +89,17 @@ public class BrightnessDialog extends Activity {
if (mShadeInteractor.isQsExpanded().getValue()) {
finish();
}
+
+ View view = findViewById(R.id.brightness_mirror_container);
+ if (view != null) {
+ collectFlow(view, mShadeInteractor.isQsExpanded(), this::onShadeStateChange);
+ }
+ }
+
+ private void onShadeStateChange(boolean isQsExpanded) {
+ if (isQsExpanded) {
+ requestFinish();
+ }
}
private void setWindowAttributes() {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
index bc5090f14d23..be1fa2bcadf9 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -227,7 +227,7 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV
mListener.onChanged(mTracking, progress, false);
SeekableSliderEventProducer eventProducer =
mBrightnessSliderHapticPlugin.getSeekableSliderEventProducer();
- if (eventProducer != null) {
+ if (eventProducer != null && fromUser) {
eventProducer.onProgressChanged(seekBar, progress, fromUser);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 17eb3c83fefe..286037ef1961 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1160,9 +1160,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
// Occluded->Lockscreen
collectFlow(mView, mKeyguardTransitionInteractor.getOccludedToLockscreenTransition(),
mOccludedToLockscreenTransition, mMainDispatcher);
- if (!KeyguardShadeMigrationNssl.isEnabled()) {
- collectFlow(mView, mOccludedToLockscreenTransitionViewModel.getLockscreenAlpha(),
+ collectFlow(mView, mOccludedToLockscreenTransitionViewModel.getLockscreenAlpha(),
setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher);
+ if (!KeyguardShadeMigrationNssl.isEnabled()) {
collectFlow(mView,
mOccludedToLockscreenTransitionViewModel.getLockscreenTranslationY(),
setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher);
@@ -1192,8 +1192,10 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mLockscreenToOccludedTransition, mMainDispatcher);
collectFlow(mView, mLockscreenToOccludedTransitionViewModel.getLockscreenAlpha(),
setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher);
- collectFlow(mView, mLockscreenToOccludedTransitionViewModel.getLockscreenTranslationY(),
- setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher);
+ if (!KeyguardShadeMigrationNssl.isEnabled()) {
+ collectFlow(mView, mLockscreenToOccludedTransitionViewModel.getLockscreenTranslationY(),
+ setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher);
+ }
// Primary bouncer->Gone (ensures lockscreen content is not visible on successful auth)
collectFlow(mView, mPrimaryBouncerToGoneTransitionViewModel.getLockscreenAlpha(),
@@ -1463,6 +1465,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
private void attachSplitShadeMediaPlayerContainer(FrameLayout container) {
+ if (migrateClocksToBlueprint()) {
+ return;
+ }
mKeyguardMediaController.attachSplitShadeContainer(container);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
index 93c55de7d480..71efbab0f738 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
@@ -44,6 +44,13 @@ public interface NotificationLockscreenUserManager {
boolean isCurrentProfile(int userId);
+ /**
+ *
+ * @param userId user Id
+ * @return true if user profile is running.
+ */
+ boolean isProfileAvailable(int userId);
+
/** Adds a listener to be notified when the current user changes. */
void addUserChangedListener(UserChangedListener listener);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 05c383910542..633510d9c438 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -461,6 +461,13 @@ public class NotificationLockscreenUserManagerImpl implements
}
}
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ public boolean isProfileAvailable(int userId) {
+ synchronized (mLock) {
+ return mUserManager.isUserRunning(userId);
+ }
+ }
+
private void setShowLockscreenNotifications(boolean show) {
mShowLockscreenNotifications = show;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
index 8fe00225463b..b76cdb8a5a3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
@@ -237,7 +237,7 @@ object NotificationIconContainerViewBinder {
// Add and bind.
val toAdd: Sequence<String> = iconsDiff.added.asSequence() + failedBindings.toList()
- for ((idx, notifKey) in toAdd.withIndex()) {
+ for (notifKey in toAdd) {
// Lookup the StatusBarIconView from the store.
val sbiv = viewStore.iconView(notifKey)
if (sbiv == null) {
@@ -256,7 +256,7 @@ object NotificationIconContainerViewBinder {
// added again.
removeTransientView(sbiv)
}
- view.addView(sbiv, idx)
+ view.addView(sbiv)
boundViewsByNotifKey.remove(notifKey)?.second?.cancel()
boundViewsByNotifKey[notifKey] =
Pair(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index eff91e55d9a8..5ee38bebc99b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -25,6 +25,8 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
+import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel
import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -71,6 +73,20 @@ constructor(
KeyguardState.PRIMARY_BOUNCER
)
+ private val lockscreenToOccludedRunning =
+ keyguardTransitionInteractor
+ .transition(KeyguardState.LOCKSCREEN, KeyguardState.OCCLUDED)
+ .map { it.transitionState == STARTED || it.transitionState == RUNNING }
+ .distinctUntilChanged()
+ .onStart { emit(false) }
+
+ private val occludedToLockscreenRunning =
+ keyguardTransitionInteractor
+ .transition(KeyguardState.OCCLUDED, KeyguardState.LOCKSCREEN)
+ .map { it.transitionState == STARTED || it.transitionState == RUNNING }
+ .distinctUntilChanged()
+ .onStart { emit(false) }
+
val shadeCollapseFadeInComplete = MutableStateFlow(false)
val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
@@ -122,7 +138,11 @@ constructor(
) { isKeyguard, isShadeVisible, qsExpansion ->
isKeyguard && !(isShadeVisible || qsExpansion)
}
- .distinctUntilChanged()
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = false,
+ )
/** Fade in only for use after the shade collapses */
val shadeCollpaseFadeIn: Flow<Boolean> =
@@ -182,26 +202,37 @@ constructor(
)
val alpha: Flow<Float> =
- isOnLockscreenWithoutShade
- .flatMapLatest { isOnLockscreenWithoutShade ->
- combineTransform(
- merge(
- occludedToLockscreenTransitionViewModel.lockscreenAlpha,
- lockscreenToOccludedTransitionViewModel.lockscreenAlpha,
- keyguardInteractor.keyguardAlpha,
- ),
- shadeCollpaseFadeIn,
- ) { alpha, shadeCollpaseFadeIn ->
- if (isOnLockscreenWithoutShade) {
- if (!shadeCollpaseFadeIn) {
- emit(alpha)
- }
+ // Due to issues with the legacy shade, some shade expansion events are sent incorrectly,
+ // such as when the shade resets. This can happen while the LOCKSCREEN<->OCCLUDED transition
+ // is running. Therefore use a series of flatmaps to prevent unwanted interruptions while
+ // those transitions are in progress. Without this, the alpha value will produce a visible
+ // flicker.
+ lockscreenToOccludedRunning.flatMapLatest { isLockscreenToOccludedRunning ->
+ if (isLockscreenToOccludedRunning) {
+ lockscreenToOccludedTransitionViewModel.lockscreenAlpha
+ } else {
+ occludedToLockscreenRunning.flatMapLatest { isOccludedToLockscreenRunning ->
+ if (isOccludedToLockscreenRunning) {
+ occludedToLockscreenTransitionViewModel.lockscreenAlpha.onStart { emit(0f) }
} else {
- emit(1f)
+ isOnLockscreenWithoutShade.flatMapLatest { isOnLockscreenWithoutShade ->
+ combineTransform(
+ keyguardInteractor.keyguardAlpha,
+ shadeCollpaseFadeIn,
+ ) { alpha, shadeCollpaseFadeIn ->
+ if (isOnLockscreenWithoutShade) {
+ if (!shadeCollpaseFadeIn) {
+ emit(alpha)
+ }
+ } else {
+ emit(1f)
+ }
+ }
+ }
}
}
}
- .distinctUntilChanged()
+ }
/**
* Under certain scenarios, such as swiping up on the lockscreen, the container will need to be
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
index 97cb45aebd13..6e8ad2e50620 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
@@ -21,13 +21,17 @@ import android.graphics.Rect
import android.os.LocaleList
import android.view.View.LAYOUT_DIRECTION_RTL
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
import javax.inject.Inject
@SysUISingleton
-class ConfigurationControllerImpl @Inject constructor(context: Context) : ConfigurationController {
+class ConfigurationControllerImpl @Inject constructor(
+ @Application context: Context,
+ ) : ConfigurationController {
- private val listeners: MutableList<ConfigurationController.ConfigurationListener> = ArrayList()
+ private val listeners: MutableList<ConfigurationListener> = ArrayList()
private val lastConfig = Configuration()
private var density: Int = 0
private var smallestScreenWidth: Int = 0
@@ -143,14 +147,12 @@ class ConfigurationControllerImpl @Inject constructor(context: Context) : Config
}
}
-
-
- override fun addCallback(listener: ConfigurationController.ConfigurationListener) {
+ override fun addCallback(listener: ConfigurationListener) {
listeners.add(listener)
listener.onDensityOrFontScaleChanged()
}
- override fun removeCallback(listener: ConfigurationController.ConfigurationListener) {
+ override fun removeCallback(listener: ConfigurationListener) {
listeners.remove(listener)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerStartable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerStartable.kt
new file mode 100644
index 000000000000..90ebaf269a39
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerStartable.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.statusbar.phone
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import javax.inject.Inject
+
+@SysUISingleton
+class ConfigurationControllerStartable
+@Inject
+constructor(
+ private val configurationController: ConfigurationController,
+ private val listeners: Set<@JvmSuppressWildcards ConfigurationListener>
+) : CoreStartable {
+ override fun start() {
+ listeners.forEach { configurationController.addCallback(it) }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
deleted file mode 100644
index 7048a78f31fc..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import android.annotation.IntDef;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.graphics.drawable.Icon;
-import android.os.UserHandle;
-
-import com.android.internal.statusbar.StatusBarIcon;
-import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
-import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Wraps {@link com.android.internal.statusbar.StatusBarIcon} so we can still have a uniform list
- */
-public class StatusBarIconHolder {
- public static final int TYPE_ICON = 0;
- /**
- * TODO (b/249790733): address this once the new pipeline is in place
- * This type exists so that the new pipeline (see {@link MobileIconViewModel}) can be used
- * to inform the old view system about changes to the data set (the list of mobile icons). The
- * design of the new pipeline should allow for removal of this icon holder type, and obsolete
- * the need for this entire class.
- *
- * @deprecated This field only exists so the new status bar pipeline can interface with the
- * view holder system.
- */
- @Deprecated
- public static final int TYPE_MOBILE_NEW = 3;
-
- /**
- * TODO (b/238425913): address this once the new pipeline is in place
- * This type exists so that the new wifi pipeline can be used to inform the old view system
- * about the existence of the wifi icon. The design of the new pipeline should allow for removal
- * of this icon holder type, and obsolete the need for this entire class.
- *
- * @deprecated This field only exists so the new status bar pipeline can interface with the
- * view holder system.
- */
- @Deprecated
- public static final int TYPE_WIFI_NEW = 4;
-
- @IntDef({
- TYPE_ICON,
- TYPE_MOBILE_NEW,
- TYPE_WIFI_NEW
- })
- @Retention(RetentionPolicy.SOURCE)
- @interface IconType {}
-
- private StatusBarIcon mIcon;
- private @IconType int mType = TYPE_ICON;
- private int mTag = 0;
-
- /** Returns a human-readable string representing the given type. */
- public static String getTypeString(@IconType int type) {
- switch(type) {
- case TYPE_ICON: return "ICON";
- case TYPE_MOBILE_NEW: return "MOBILE_NEW";
- case TYPE_WIFI_NEW: return "WIFI_NEW";
- default: return "UNKNOWN";
- }
- }
-
- private StatusBarIconHolder() {
- }
-
- public static StatusBarIconHolder fromIcon(StatusBarIcon icon) {
- StatusBarIconHolder wrapper = new StatusBarIconHolder();
- wrapper.mIcon = icon;
-
- return wrapper;
- }
-
- /** Creates a new holder with for the new wifi icon. */
- public static StatusBarIconHolder forNewWifiIcon() {
- StatusBarIconHolder holder = new StatusBarIconHolder();
- holder.mType = TYPE_WIFI_NEW;
- return holder;
- }
-
- /**
- * ONLY for use with the new connectivity pipeline, where we only need a subscriptionID to
- * determine icon ordering and building the correct view model
- */
- public static StatusBarIconHolder fromSubIdForModernMobileIcon(int subId) {
- StatusBarIconHolder holder = new StatusBarIconHolder();
- holder.mType = TYPE_MOBILE_NEW;
- holder.mTag = subId;
-
- return holder;
- }
-
- /**
- * Creates a new StatusBarIconHolder from a CallIndicatorIconState.
- */
- public static StatusBarIconHolder fromCallIndicatorState(
- Context context,
- CallIndicatorIconState state) {
- StatusBarIconHolder holder = new StatusBarIconHolder();
- int resId = state.isNoCalling ? state.noCallingResId : state.callStrengthResId;
- String contentDescription = state.isNoCalling
- ? state.noCallingDescription : state.callStrengthDescription;
- holder.mIcon = new StatusBarIcon(UserHandle.SYSTEM, context.getPackageName(),
- Icon.createWithResource(context, resId), 0, 0, contentDescription);
- holder.mTag = state.subId;
- return holder;
- }
-
- public @IconType int getType() {
- return mType;
- }
-
- @Nullable
- public StatusBarIcon getIcon() {
- return mIcon;
- }
-
- public void setIcon(StatusBarIcon icon) {
- mIcon = icon;
- }
-
- public boolean isVisible() {
- switch (mType) {
- case TYPE_ICON:
- return mIcon.visible;
- case TYPE_MOBILE_NEW:
- case TYPE_WIFI_NEW:
- // The new pipeline controls visibilities via the view model and view binder, so
- // this is effectively an unused return value.
- return true;
- default:
- return true;
- }
- }
-
- public void setVisible(boolean visible) {
- if (isVisible() == visible) {
- return;
- }
-
- switch (mType) {
- case TYPE_ICON:
- mIcon.visible = visible;
- break;
-
- case TYPE_MOBILE_NEW:
- case TYPE_WIFI_NEW:
- // The new pipeline controls visibilities via the view model and view binder, so
- // ignore setVisible.
- break;
- }
- }
-
- public int getTag() {
- return mTag;
- }
-
- @Override
- public String toString() {
- return "StatusBarIconHolder(type=" + getTypeString(mType)
- + " tag=" + getTag()
- + " visible=" + isVisible() + ")";
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.kt
new file mode 100644
index 000000000000..5b55a1e73dc3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.kt
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.phone
+
+import android.annotation.IntDef
+import android.content.Context
+import android.graphics.drawable.Icon
+import android.os.UserHandle
+import com.android.internal.statusbar.StatusBarIcon
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState
+
+/** Wraps [com.android.internal.statusbar.StatusBarIcon] so we can still have a uniform list */
+class StatusBarIconHolder private constructor() {
+ @IntDef(TYPE_ICON, TYPE_MOBILE_NEW, TYPE_WIFI_NEW)
+ @Retention(AnnotationRetention.SOURCE)
+ internal annotation class IconType
+
+ var icon: StatusBarIcon? = null
+
+ @IconType
+ var type = TYPE_ICON
+ private set
+
+ var tag = 0
+ private set
+
+ var isVisible: Boolean
+ get() =
+ when (type) {
+ TYPE_ICON -> icon!!.visible
+
+ // The new pipeline controls visibilities via the view model and
+ // view binder, so
+ // this is effectively an unused return value.
+ TYPE_MOBILE_NEW,
+ TYPE_WIFI_NEW -> true
+ else -> true
+ }
+ set(visible) {
+ if (isVisible == visible) {
+ return
+ }
+ when (type) {
+ TYPE_ICON -> icon!!.visible = visible
+ TYPE_MOBILE_NEW,
+ TYPE_WIFI_NEW -> {}
+ }
+ }
+
+ override fun toString(): String {
+ return ("StatusBarIconHolder(type=${getTypeString(type)}" +
+ " tag=$tag" +
+ " visible=$isVisible)")
+ }
+
+ companion object {
+ const val TYPE_ICON = 0
+
+ /**
+ * TODO (b/249790733): address this once the new pipeline is in place This type exists so
+ * that the new pipeline (see [MobileIconViewModel]) can be used to inform the old view
+ * system about changes to the data set (the list of mobile icons). The design of the new
+ * pipeline should allow for removal of this icon holder type, and obsolete the need for
+ * this entire class.
+ */
+ @Deprecated(
+ """This field only exists so the new status bar pipeline can interface with the
+ view holder system."""
+ )
+ const val TYPE_MOBILE_NEW = 3
+
+ /**
+ * TODO (b/238425913): address this once the new pipeline is in place This type exists so
+ * that the new wifi pipeline can be used to inform the old view system about the existence
+ * of the wifi icon. The design of the new pipeline should allow for removal of this icon
+ * holder type, and obsolete the need for this entire class.
+ */
+ @Deprecated(
+ """This field only exists so the new status bar pipeline can interface with the
+ view holder system."""
+ )
+ const val TYPE_WIFI_NEW = 4
+
+ /** Returns a human-readable string representing the given type. */
+ fun getTypeString(@IconType type: Int): String {
+ return when (type) {
+ TYPE_ICON -> "ICON"
+ TYPE_MOBILE_NEW -> "MOBILE_NEW"
+ TYPE_WIFI_NEW -> "WIFI_NEW"
+ else -> "UNKNOWN"
+ }
+ }
+
+ @JvmStatic
+ fun fromIcon(icon: StatusBarIcon?): StatusBarIconHolder {
+ val wrapper = StatusBarIconHolder()
+ wrapper.icon = icon
+ return wrapper
+ }
+
+ /** Creates a new holder with for the new wifi icon. */
+ @JvmStatic
+ fun forNewWifiIcon(): StatusBarIconHolder {
+ val holder = StatusBarIconHolder()
+ holder.type = TYPE_WIFI_NEW
+ return holder
+ }
+
+ /**
+ * ONLY for use with the new connectivity pipeline, where we only need a subscriptionID to
+ * determine icon ordering and building the correct view model
+ */
+ @JvmStatic
+ fun fromSubIdForModernMobileIcon(subId: Int): StatusBarIconHolder {
+ val holder = StatusBarIconHolder()
+ holder.type = TYPE_MOBILE_NEW
+ holder.tag = subId
+ return holder
+ }
+
+ /** Creates a new StatusBarIconHolder from a CallIndicatorIconState. */
+ @JvmStatic
+ fun fromCallIndicatorState(
+ context: Context,
+ state: CallIndicatorIconState,
+ ): StatusBarIconHolder {
+ val holder = StatusBarIconHolder()
+ val resId = if (state.isNoCalling) state.noCallingResId else state.callStrengthResId
+ val contentDescription =
+ if (state.isNoCalling) state.noCallingDescription else state.callStrengthDescription
+ holder.icon =
+ StatusBarIcon(
+ UserHandle.SYSTEM,
+ context.packageName,
+ Icon.createWithResource(context, resId),
+ 0,
+ 0,
+ contentDescription,
+ )
+ holder.tag = state.subId
+ return holder
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index 942d186e7005..b048da492eb1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -16,12 +16,16 @@
package com.android.systemui.statusbar.phone.dagger;
+import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.CentralSurfacesImpl;
+import com.android.systemui.statusbar.phone.ConfigurationControllerStartable;
import dagger.Binds;
import dagger.Module;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
/**
* Dagger Module providing {@link CentralSurfacesImpl}.
@@ -34,4 +38,12 @@ public interface StatusBarPhoneModule {
@Binds
@SysUISingleton
CentralSurfaces bindsCentralSurfaces(CentralSurfacesImpl impl);
+
+ /**
+ * Starts {@link ConfigurationControllerStartable}
+ */
+ @Binds
+ @IntoMap
+ @ClassKey(ConfigurationControllerStartable.class)
+ CoreStartable bindConfigControllerStartable(ConfigurationControllerStartable impl);
}
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastModule.kt b/packages/SystemUI/src/com/android/systemui/toast/ToastModule.kt
new file mode 100644
index 000000000000..6cd999320236
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastModule.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.toast
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
+
+@Module
+interface ToastModule {
+ /** Starts ToastUI. */
+ @Binds
+ @IntoMap
+ @ClassKey(ToastUI::class)
+ fun bindToastUIStartable(service: ToastUI): CoreStartable
+
+ /** Listen to config changes for ToastUI. */
+ @Binds @IntoSet fun bindToastUIConfigChanges(service: ToastUI): ConfigurationListener
+}
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
index 27f8121e6e66..85a455d23d49 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
@@ -42,6 +42,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import java.util.Objects;
@@ -51,7 +52,10 @@ import javax.inject.Inject;
* Controls display of text toasts.
*/
@SysUISingleton
-public class ToastUI implements CoreStartable, CommandQueue.Callbacks {
+public class ToastUI implements
+ CoreStartable,
+ ConfigurationController.ConfigurationListener,
+ CommandQueue.Callbacks {
// values from NotificationManagerService#LONG_DELAY and NotificationManagerService#SHORT_DELAY
private static final int TOAST_LONG_TIME = 3500; // 3.5 seconds
private static final int TOAST_SHORT_TIME = 2000; // 2 seconds
@@ -187,7 +191,7 @@ public class ToastUI implements CoreStartable, CommandQueue.Callbacks {
}
@Override
- public void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigChanged(Configuration newConfig) {
if (newConfig.orientation != mOrientation) {
mOrientation = newConfig.orientation;
if (mToast != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
index 472f0ae364c5..cf6b0d9d1bfe 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
@@ -5,9 +5,7 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dagger.qualifiers.Tracing
-import com.android.systemui.Flags.coroutineTracing
-import com.android.app.tracing.TraceUtils.Companion.coroutineTracingIsEnabled
-import com.android.app.tracing.TraceContextElement
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.CoroutineDispatcher
@@ -16,7 +14,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.plus
import kotlin.coroutines.CoroutineContext
-import kotlin.coroutines.EmptyCoroutineContext
/** Providers for various coroutines-related constructs. */
@Module
@@ -83,9 +80,6 @@ class CoroutinesModule {
@Tracing
@SysUISingleton
fun tracingCoroutineContext(): CoroutineContext {
- return if (coroutineTracing()) {
- coroutineTracingIsEnabled = true
- TraceContextElement()
- } else EmptyCoroutineContext
+ return createCoroutineTracingContext()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index 3451ae0a9be3..dc2b80c5e391 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -22,16 +22,17 @@ import android.os.Handler;
import android.util.Log;
import com.android.systemui.CoreStartable;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.qs.tiles.DndTile;
+import com.android.systemui.res.R;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import java.io.PrintWriter;
import javax.inject.Inject;
@SysUISingleton
-public class VolumeUI implements CoreStartable {
+public class VolumeUI implements CoreStartable, ConfigurationController.ConfigurationListener {
private static final String TAG = "VolumeUI";
private static boolean LOGD = Log.isLoggable(TAG, Log.DEBUG);
@@ -60,7 +61,7 @@ public class VolumeUI implements CoreStartable {
}
@Override
- public void onConfigurationChanged(Configuration newConfig) {
+ public void onConfigChanged(Configuration newConfig) {
if (!mEnabled) return;
mVolumeComponent.onConfigurationChanged(newConfig);
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index 53217d481599..8d06a8f3afd8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -21,6 +21,7 @@ import android.media.AudioManager;
import android.os.Looper;
import com.android.internal.jank.InteractionJankMonitor;
+import com.android.systemui.CoreStartable;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.ActivityStarter;
@@ -36,15 +37,30 @@ import com.android.systemui.volume.VolumeComponent;
import com.android.systemui.volume.VolumeDialogComponent;
import com.android.systemui.volume.VolumeDialogImpl;
import com.android.systemui.volume.VolumePanelFactory;
+import com.android.systemui.volume.VolumeUI;
import dagger.Binds;
import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
+import dagger.multibindings.IntoSet;
/** Dagger Module for code in the volume package. */
@Module
public interface VolumeModule {
+ /** Starts VolumeUI. */
+ @Binds
+ @IntoMap
+ @ClassKey(VolumeUI.class)
+ CoreStartable bindVolumeUIStartable(VolumeUI impl);
+
+ /** Listen to config changes for VolumeUI. */
+ @Binds
+ @IntoSet
+ ConfigurationController.ConfigurationListener bindVolumeUIConfigChanges(VolumeUI impl);
+
/** */
@Binds
VolumeComponent provideVolumeComponent(VolumeDialogComponent volumeDialogComponent);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 639276e302a7..b3eab8a5b79f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -248,8 +248,8 @@ public class ScreenDecorationsTest extends SysuiTestCase {
}
@Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
+ public void onConfigChanged(Configuration newConfig) {
+ super.onConfigChanged(newConfig);
mExecutor.runAllReady();
}
@@ -892,7 +892,7 @@ public class ScreenDecorationsTest extends SysuiTestCase {
// Switch to long edge cutout(left).
mMockCutoutList.set(0, new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT));
- mScreenDecorations.onConfigurationChanged(new Configuration());
+ mScreenDecorations.onConfigChanged(new Configuration());
verifyOverlaysExistAndAdded(true, false, false, false, View.VISIBLE);
}
@@ -913,7 +913,7 @@ public class ScreenDecorationsTest extends SysuiTestCase {
// Switch to long edge cutout(left).
mMockCutoutList.set(0, new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT));
- mScreenDecorations.onConfigurationChanged(new Configuration());
+ mScreenDecorations.onConfigChanged(new Configuration());
verifyOverlaysExistAndAdded(true, false, true, false, View.VISIBLE);
verify(mDotViewController, times(2)).initialize(any(), any(), any(), any());
verify(mDotViewController, times(2)).setShowingListener(null);
@@ -949,7 +949,7 @@ public class ScreenDecorationsTest extends SysuiTestCase {
// top cutout
mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));
- mScreenDecorations.onConfigurationChanged(new Configuration());
+ mScreenDecorations.onConfigChanged(new Configuration());
// Only top windows should be added.
verifyOverlaysExistAndAdded(false, true, false, false, View.VISIBLE);
@@ -976,7 +976,7 @@ public class ScreenDecorationsTest extends SysuiTestCase {
// top cutout
mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));
- mScreenDecorations.onConfigurationChanged(new Configuration());
+ mScreenDecorations.onConfigChanged(new Configuration());
// Both top and bottom windows should be added with VISIBLE because of privacy dot and
// cutout, but rounded corners visibility shall be gone because of no rounding.
@@ -1013,7 +1013,7 @@ public class ScreenDecorationsTest extends SysuiTestCase {
doReturn(2f).when(mScreenDecorations).getPhysicalPixelDisplaySizeRatio();
mDisplayInfo.rotation = Surface.ROTATION_270;
- mScreenDecorations.onConfigurationChanged(null);
+ mScreenDecorations.onConfigChanged(null);
assertEquals(new Size(6, 6), resDelegate.getTopRoundedSize());
assertEquals(new Size(8, 8), resDelegate.getBottomRoundedSize());
@@ -1145,7 +1145,7 @@ public class ScreenDecorationsTest extends SysuiTestCase {
assertThat(mScreenDecorations.mIsRegistered, is(false));
doReturn(true).when(mScreenDecorations).hasOverlays();
- mScreenDecorations.onConfigurationChanged(new Configuration());
+ mScreenDecorations.onConfigChanged(new Configuration());
assertThat(mScreenDecorations.mIsRegistered, is(true));
}
@@ -1156,7 +1156,7 @@ public class ScreenDecorationsTest extends SysuiTestCase {
mScreenDecorations.start();
assertThat(mScreenDecorations.mIsRegistered, is(true));
- mScreenDecorations.onConfigurationChanged(new Configuration());
+ mScreenDecorations.onConfigChanged(new Configuration());
assertThat(mScreenDecorations.mIsRegistered, is(true));
}
@@ -1168,7 +1168,7 @@ public class ScreenDecorationsTest extends SysuiTestCase {
assertThat(mScreenDecorations.mIsRegistered, is(true));
doReturn(false).when(mScreenDecorations).hasOverlays();
- mScreenDecorations.onConfigurationChanged(new Configuration());
+ mScreenDecorations.onConfigChanged(new Configuration());
assertThat(mScreenDecorations.mIsRegistered, is(false));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
index f8856c968a23..bd49927c8b53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
@@ -102,8 +102,9 @@ public class IMagnificationConnectionTest extends SysuiTestCase {
getContext().getMainThreadHandler(), mCommandQueue,
mModeSwitchesController, mSysUiState, mOverviewProxyService, mSecureSettings,
mDisplayTracker, getContext().getSystemService(DisplayManager.class), mA11yLogger);
- mMagnification.mMagnificationControllerSupplier = new FakeControllerSupplier(
- mContext.getSystemService(DisplayManager.class));
+ mMagnification.mWindowMagnificationControllerSupplier =
+ new FakeWindowMagnificationControllerSupplier(
+ mContext.getSystemService(DisplayManager.class));
mMagnification.mMagnificationSettingsSupplier = new FakeSettingsSupplier(
mContext.getSystemService(DisplayManager.class));
@@ -201,10 +202,10 @@ public class IMagnificationConnectionTest extends SysuiTestCase {
verify(mMagnificationSettingsController).setMagnificationScale(eq(testScale));
}
- private class FakeControllerSupplier extends
+ private class FakeWindowMagnificationControllerSupplier extends
DisplayIdIndexSupplier<WindowMagnificationController> {
- FakeControllerSupplier(DisplayManager displayManager) {
+ FakeWindowMagnificationControllerSupplier(DisplayManager displayManager) {
super(displayManager);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
index d0e1678739c4..3b5cbea079a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
@@ -124,7 +124,7 @@ public class MagnificationTest extends SysuiTestCase {
getContext().getMainThreadHandler(), mCommandQueue, mModeSwitchesController,
mSysUiState, mOverviewProxyService, mSecureSettings, mDisplayTracker,
getContext().getSystemService(DisplayManager.class), mA11yLogger);
- mMagnification.mMagnificationControllerSupplier = new FakeControllerSupplier(
+ mMagnification.mWindowMagnificationControllerSupplier = new FakeControllerSupplier(
mContext.getSystemService(DisplayManager.class), mWindowMagnificationController);
mMagnification.mMagnificationSettingsSupplier = new FakeSettingsSupplier(
mContext.getSystemService(DisplayManager.class), mMagnificationSettingsController);
@@ -325,9 +325,9 @@ public class MagnificationTest extends SysuiTestCase {
@Test
public void overviewProxyIsConnected_controllerIsAvailable_updateSysUiStateFlag() {
final WindowMagnificationController mController = mock(WindowMagnificationController.class);
- mMagnification.mMagnificationControllerSupplier = new FakeControllerSupplier(
+ mMagnification.mWindowMagnificationControllerSupplier = new FakeControllerSupplier(
mContext.getSystemService(DisplayManager.class), mController);
- mMagnification.mMagnificationControllerSupplier.get(TEST_DISPLAY);
+ mMagnification.mWindowMagnificationControllerSupplier.get(TEST_DISPLAY);
mOverviewProxyListener.onConnectionChanged(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
index fd258e38a00f..9bcab57bec87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
@@ -40,12 +40,12 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
-/** Tests for {@link DismissAnimationController}. */
+/** Tests for {@link DragToInteractAnimationController}. */
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
-public class DismissAnimationControllerTest extends SysuiTestCase {
- private DismissAnimationController mDismissAnimationController;
+public class DragToInteractAnimationControllerTest extends SysuiTestCase {
+ private DragToInteractAnimationController mDragToInteractAnimationController;
private DismissView mDismissView;
@Rule
@@ -65,19 +65,20 @@ public class DismissAnimationControllerTest extends SysuiTestCase {
stubMenuViewAppearance);
mDismissView = spy(new DismissView(mContext));
DismissViewUtils.setup(mDismissView);
- mDismissAnimationController = new DismissAnimationController(mDismissView, stubMenuView);
+ mDragToInteractAnimationController = new DragToInteractAnimationController(
+ mDismissView, stubMenuView);
}
@Test
public void showDismissView_success() {
- mDismissAnimationController.showDismissView(true);
+ mDragToInteractAnimationController.showDismissView(true);
verify(mDismissView).show();
}
@Test
public void hideDismissView_success() {
- mDismissAnimationController.showDismissView(false);
+ mDragToInteractAnimationController.showDismissView(false);
verify(mDismissView).hide();
}
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 7f12c05ad28a..9c8de302c5e1 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
@@ -62,7 +62,7 @@ public class MenuItemAccessibilityDelegateTest extends SysuiTestCase {
@Mock
private SecureSettings mSecureSettings;
@Mock
- private DismissAnimationController.DismissCallback mStubDismissCallback;
+ private DragToInteractAnimationController.DismissCallback mStubDismissCallback;
private RecyclerView mStubListView;
private MenuView mMenuView;
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 9797f2ab45db..e1522f5f6751 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
@@ -68,7 +68,7 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
private MenuView mStubMenuView;
private MenuListViewTouchHandler mTouchHandler;
private MenuAnimationController mMenuAnimationController;
- private DismissAnimationController mDismissAnimationController;
+ private DragToInteractAnimationController mDragToInteractAnimationController;
private RecyclerView mStubListView;
private DismissView mDismissView;
@@ -92,10 +92,10 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
mStubMenuView, stubMenuViewAppearance));
mDismissView = spy(new DismissView(mContext));
DismissViewUtils.setup(mDismissView);
- mDismissAnimationController =
- spy(new DismissAnimationController(mDismissView, mStubMenuView));
+ mDragToInteractAnimationController =
+ spy(new DragToInteractAnimationController(mDismissView, mStubMenuView));
mTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController,
- mDismissAnimationController);
+ mDragToInteractAnimationController);
final AccessibilityTargetAdapter stubAdapter = new AccessibilityTargetAdapter(mStubTargets);
mStubListView = (RecyclerView) mStubMenuView.getChildAt(0);
mStubListView.setAdapter(stubAdapter);
@@ -115,7 +115,7 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
@Test
public void onActionMoveEvent_notConsumedEvent_shouldMoveToPosition() {
- doReturn(false).when(mDismissAnimationController).maybeConsumeMoveMotionEvent(
+ doReturn(false).when(mDragToInteractAnimationController).maybeConsumeMoveMotionEvent(
any(MotionEvent.class));
final int offset = 100;
final MotionEvent stubDownEvent =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
index e2aa984de46b..647dae6fd6c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
@@ -20,10 +20,9 @@ import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import org.junit.Assert.assertFalse
import org.junit.Before
@@ -42,8 +41,7 @@ class UdfpsBpViewControllerTest : SysuiTestCase() {
@Mock lateinit var udfpsBpView: UdfpsBpView
@Mock lateinit var statusBarStateController: StatusBarStateController
- @Mock lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
- @Mock lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
+ @Mock lateinit var shadeInteractor: ShadeInteractor
@Mock lateinit var systemUIDialogManager: SystemUIDialogManager
@Mock lateinit var dumpManager: DumpManager
@@ -55,7 +53,7 @@ class UdfpsBpViewControllerTest : SysuiTestCase() {
UdfpsBpViewController(
udfpsBpView,
statusBarStateController,
- primaryBouncerInteractor,
+ shadeInteractor,
systemUIDialogManager,
dumpManager
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
index b4ae00ddd608..42d2c98f324c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
@@ -217,6 +217,7 @@ class SideFpsOverlayViewBinderTest : SysuiTestCase() {
deviceEntrySideFpsOverlayInteractor =
DeviceEntrySideFpsOverlayInteractor(
+ testScope.backgroundScope,
mContext,
deviceEntryFingerprintAuthRepository,
primaryBouncerInteractor,
@@ -260,14 +261,14 @@ class SideFpsOverlayViewBinderTest : SysuiTestCase() {
SideFpsOverlayViewBinder(
testScope.backgroundScope,
mContext,
- biometricStatusInteractor,
- displayStateInteractor,
- deviceEntrySideFpsOverlayInteractor,
- fpsUnlockTracker,
- layoutInflater,
- sideFpsProgressBarViewModel,
- sfpsSensorInteractor,
- windowManager
+ { biometricStatusInteractor },
+ { displayStateInteractor },
+ { deviceEntrySideFpsOverlayInteractor },
+ { fpsUnlockTracker },
+ { layoutInflater },
+ { sideFpsProgressBarViewModel },
+ { sfpsSensorInteractor },
+ { windowManager }
)
context.addMockSystemService(DisplayManager::class.java, displayManager)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
index 2267bdcafad8..983e4b3a9922 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
@@ -220,6 +220,7 @@ class SideFpsOverlayViewModelTest : SysuiTestCase() {
deviceEntrySideFpsOverlayInteractor =
DeviceEntrySideFpsOverlayInteractor(
+ testScope.backgroundScope,
mContext,
deviceEntryFingerprintAuthRepository,
primaryBouncerInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
index 16b2ed633f11..9c5cd713dffb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
@@ -140,21 +140,76 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
}
assertThat(widgets())
.containsExactly(
+ communalItemRankEntry2,
+ communalWidgetItemEntry2,
communalItemRankEntry1,
communalWidgetItemEntry1,
- communalItemRankEntry2,
+ )
+ .inOrder()
+
+ // swapped priorities
+ val widgetIdsToPriorityMap = mapOf(widgetInfo1.widgetId to 2, widgetInfo2.widgetId to 1)
+ communalWidgetDao.updateWidgetOrder(widgetIdsToPriorityMap)
+ assertThat(widgets())
+ .containsExactly(
+ communalItemRankEntry1.copy(rank = 2),
+ communalWidgetItemEntry1,
+ communalItemRankEntry2.copy(rank = 1),
communalWidgetItemEntry2
)
+ .inOrder()
+ }
- val widgetIdsInNewOrder = listOf(widgetInfo2.widgetId, widgetInfo1.widgetId)
- communalWidgetDao.updateWidgetOrder(widgetIdsInNewOrder)
+ @Test
+ fun addNewWidgetWithReorder_emitsWidgetsInNewOrder() =
+ testScope.runTest {
+ val existingWidgets = listOf(widgetInfo1, widgetInfo2)
+ val widgets = collectLastValue(communalWidgetDao.getWidgets())
+
+ existingWidgets.forEach {
+ val (widgetId, provider, priority) = it
+ communalWidgetDao.addWidget(
+ widgetId = widgetId,
+ provider = provider,
+ priority = priority,
+ )
+ }
assertThat(widgets())
.containsExactly(
communalItemRankEntry2,
communalWidgetItemEntry2,
communalItemRankEntry1,
- communalWidgetItemEntry1
+ communalWidgetItemEntry1,
+ )
+ .inOrder()
+
+ // map with no item in the middle at index 1
+ val widgetIdsToIndexMap = mapOf(widgetInfo1.widgetId to 1, widgetInfo2.widgetId to 3)
+ communalWidgetDao.updateWidgetOrder(widgetIdsToIndexMap)
+ assertThat(widgets())
+ .containsExactly(
+ communalItemRankEntry2.copy(rank = 3),
+ communalWidgetItemEntry2,
+ communalItemRankEntry1.copy(rank = 1),
+ communalWidgetItemEntry1,
)
+ .inOrder()
+ // add the new middle item that we left space for.
+ communalWidgetDao.addWidget(
+ widgetId = widgetInfo3.widgetId,
+ provider = widgetInfo3.provider,
+ priority = 2,
+ )
+ assertThat(widgets())
+ .containsExactly(
+ communalItemRankEntry2.copy(rank = 3),
+ communalWidgetItemEntry2,
+ communalItemRankEntry3.copy(rank = 2),
+ communalWidgetItemEntry3,
+ communalItemRankEntry1.copy(rank = 1),
+ communalWidgetItemEntry1,
+ )
+ .inOrder()
}
data class FakeWidgetMetadata(
@@ -176,8 +231,15 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
provider = ComponentName("pk_name", "cls_name_2"),
priority = 2
)
+ val widgetInfo3 =
+ FakeWidgetMetadata(
+ widgetId = 3,
+ provider = ComponentName("pk_name", "cls_name_3"),
+ priority = 3
+ )
val communalItemRankEntry1 = CommunalItemRank(uid = 1L, rank = widgetInfo1.priority)
val communalItemRankEntry2 = CommunalItemRank(uid = 2L, rank = widgetInfo2.priority)
+ val communalItemRankEntry3 = CommunalItemRank(uid = 3L, rank = widgetInfo3.priority)
val communalWidgetItemEntry1 =
CommunalWidgetItem(
uid = 1L,
@@ -192,5 +254,12 @@ class CommunalWidgetDaoTest : SysuiTestCase() {
componentName = widgetInfo2.provider.flattenToString(),
itemId = communalItemRankEntry2.uid,
)
+ val communalWidgetItemEntry3 =
+ CommunalWidgetItem(
+ uid = 3L,
+ widgetId = widgetInfo3.widgetId,
+ componentName = widgetInfo3.provider.flattenToString(),
+ itemId = communalItemRankEntry3.uid,
+ )
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt
index 71a56cd8588c..c22d35cb214f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt
@@ -123,4 +123,25 @@ class SeekableSliderEventProducerTest : SysuiTestCase() {
assertEquals(SliderEvent(SliderEventType.STOPPED_TRACKING_TOUCH, 0.5F), latest)
}
+
+ @Test
+ fun onArrowUp_afterStartTrackingTouch_ArrowUpProduced() = runTest {
+ val latest by collectLastValue(eventFlow)
+
+ eventProducer.onStartTrackingTouch(seekBar)
+ eventProducer.onArrowUp()
+
+ assertEquals(SliderEvent(SliderEventType.ARROW_UP, 0f), latest)
+ }
+
+ @Test
+ fun onArrowUp_afterChangeByProgram_ArrowUpProduced_withProgress() = runTest {
+ val progress = 50
+ val latest by collectLastValue(eventFlow)
+
+ eventProducer.onProgressChanged(seekBar, progress, false)
+ eventProducer.onArrowUp()
+
+ assertEquals(SliderEvent(SliderEventType.ARROW_UP, 0.5f), latest)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
index 8d12e491ad11..db0496227a38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
@@ -528,6 +528,194 @@ class SeekableSliderTrackerTest : SysuiTestCase() {
verifyNoMoreInteractions(sliderStateListener)
}
+ @Test
+ fun onProgressChangeByProgram_atTheMiddle_onIdle_movesToArrowHandleMovedOnce() = runTest {
+ // GIVEN an initialized tracker in the IDLE state
+ initTracker(testScheduler)
+
+ // GIVEN a progress due to an external source that lands at the middle of the slider
+ val progress = 0.5f
+ sliderEventProducer.sendEvent(
+ SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+ )
+
+ // THEN the state moves to ARROW_HANDLE_MOVED_ONCE and the listener is called to play
+ // haptics
+ assertThat(mSeekableSliderTracker.currentState)
+ .isEqualTo(SliderState.ARROW_HANDLE_MOVED_ONCE)
+ verify(sliderStateListener).onSelectAndArrow(progress)
+ }
+
+ @Test
+ fun onProgressChangeByProgram_atUpperBookend_onIdle_movesToIdle() = runTest {
+ // GIVEN an initialized tracker in the IDLE state
+ val config = SeekableSliderTrackerConfig()
+ initTracker(testScheduler, config)
+
+ // GIVEN a progress due to an external source that lands at the upper bookend
+ val progress = config.upperBookendThreshold + 0.01f
+ sliderEventProducer.sendEvent(
+ SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+ )
+
+ // THEN the tracker executes upper bookend haptics before moving back to IDLE
+ verify(sliderStateListener).onUpperBookend()
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+ }
+
+ @Test
+ fun onProgressChangeByProgram_atLowerBookend_onIdle_movesToIdle() = runTest {
+ // GIVEN an initialized tracker in the IDLE state
+ val config = SeekableSliderTrackerConfig()
+ initTracker(testScheduler, config)
+
+ // WHEN a progress is recorded due to an external source that lands at the lower bookend
+ val progress = config.lowerBookendThreshold - 0.01f
+ sliderEventProducer.sendEvent(
+ SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+ )
+
+ // THEN the tracker executes lower bookend haptics before moving to IDLE
+ verify(sliderStateListener).onLowerBookend()
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+ }
+
+ @Test
+ fun onArrowUp_onArrowMovedOnce_movesToIdle() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVED_ONCE state
+ initTracker(testScheduler)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVED_ONCE)
+
+ // WHEN the external stimulus is released
+ val progress = 0.5f
+ sliderEventProducer.sendEvent(SliderEvent(SliderEventType.ARROW_UP, progress))
+
+ // THEN the tracker moves back to IDLE and there are no haptics
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+ verifyZeroInteractions(sliderStateListener)
+ }
+
+ @Test
+ fun onStartTrackingTouch_onArrowMovedOnce_movesToWait() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVED_ONCE state
+ initTracker(testScheduler)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVED_ONCE)
+
+ // WHEN the slider starts tracking touch
+ val progress = 0.5f
+ sliderEventProducer.sendEvent(SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, progress))
+
+ // THEN the tracker moves back to WAIT and starts the waiting job. Also, there are no
+ // haptics
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.WAIT)
+ assertThat(mSeekableSliderTracker.isWaiting).isTrue()
+ verifyZeroInteractions(sliderStateListener)
+ }
+
+ @Test
+ fun onProgressChangeByProgram_onArrowMovedOnce_movesToArrowMovesContinuously() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVED_ONCE state
+ initTracker(testScheduler)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVED_ONCE)
+
+ // WHEN the slider gets an external progress change
+ val progress = 0.5f
+ sliderEventProducer.sendEvent(
+ SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+ )
+
+ // THEN the tracker moves to ARROW_HANDLE_MOVES_CONTINUOUSLY and calls the appropriate
+ // haptics
+ assertThat(mSeekableSliderTracker.currentState)
+ .isEqualTo(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+ verify(sliderStateListener).onProgress(progress)
+ }
+
+ @Test
+ fun onArrowUp_onArrowMovesContinuously_movesToIdle() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+ initTracker(testScheduler)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+ // WHEN the external stimulus is released
+ val progress = 0.5f
+ sliderEventProducer.sendEvent(SliderEvent(SliderEventType.ARROW_UP, progress))
+
+ // THEN the tracker moves to IDLE and no haptics are played
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+ verifyZeroInteractions(sliderStateListener)
+ }
+
+ @Test
+ fun onStartTrackingTouch_onArrowMovesContinuously_movesToWait() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+ initTracker(testScheduler)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+ // WHEN the slider starts tracking touch
+ val progress = 0.5f
+ sliderEventProducer.sendEvent(SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, progress))
+
+ // THEN the tracker moves to WAIT and the wait job starts.
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.WAIT)
+ assertThat(mSeekableSliderTracker.isWaiting).isTrue()
+ verifyZeroInteractions(sliderStateListener)
+ }
+
+ @Test
+ fun onProgressChangeByProgram_onArrowMovesContinuously_preservesState() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+ initTracker(testScheduler)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+ // WHEN the slider changes progress programmatically at the middle
+ val progress = 0.5f
+ sliderEventProducer.sendEvent(
+ SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+ )
+
+ // THEN the tracker stays in the same state and haptics are delivered appropriately
+ assertThat(mSeekableSliderTracker.currentState)
+ .isEqualTo(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+ verify(sliderStateListener).onProgress(progress)
+ }
+
+ @Test
+ fun onProgramProgress_atLowerBookend_onArrowMovesContinuously_movesToIdle() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+ val config = SeekableSliderTrackerConfig()
+ initTracker(testScheduler, config)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+ // WHEN the slider reaches the lower bookend programmatically
+ val progress = config.lowerBookendThreshold - 0.01f
+ sliderEventProducer.sendEvent(
+ SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+ )
+
+ // THEN the tracker executes lower bookend haptics before moving to IDLE
+ verify(sliderStateListener).onLowerBookend()
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+ }
+
+ @Test
+ fun onProgramProgress_atUpperBookend_onArrowMovesContinuously_movesToIdle() = runTest {
+ // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
+ val config = SeekableSliderTrackerConfig()
+ initTracker(testScheduler, config)
+ mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
+
+ // WHEN the slider reaches the lower bookend programmatically
+ val progress = config.upperBookendThreshold + 0.01f
+ sliderEventProducer.sendEvent(
+ SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress)
+ )
+
+ // THEN the tracker executes upper bookend haptics before moving to IDLE
+ verify(sliderStateListener).onUpperBookend()
+ assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
+ }
+
@OptIn(ExperimentalCoroutinesApi::class)
private fun initTracker(
scheduler: TestCoroutineScheduler,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
index 0616a3471bbe..027dfa15f812 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
@@ -109,6 +109,7 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() {
)
underTest =
DeviceEntrySideFpsOverlayInteractor(
+ testScope.backgroundScope,
mContext,
FakeDeviceEntryFingerprintAuthRepository(),
primaryBouncerInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
index 8532ffed85fb..94b9fa4a6582 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
@@ -55,6 +55,7 @@ private const val KEY = "TEST_KEY"
private const val KEY_ALT = "TEST_KEY_2"
private const val USER_MAIN = 0
private const val USER_GUEST = 10
+private const val PRIVATE_PROFILE = 12
private const val PACKAGE = "PKG"
private val INSTANCE_ID = InstanceId.fakeInstanceId(123)!!
private const val APP_UID = 99
@@ -82,6 +83,7 @@ class MediaDataFilterTest : SysuiTestCase() {
private lateinit var mediaDataFilter: MediaDataFilter
private lateinit var dataMain: MediaData
private lateinit var dataGuest: MediaData
+ private lateinit var dataPrivateProfile: MediaData
private val clock = FakeSystemClock()
@Before
@@ -115,6 +117,7 @@ class MediaDataFilterTest : SysuiTestCase() {
appUid = APP_UID
)
dataGuest = dataMain.copy(userId = USER_GUEST)
+ dataPrivateProfile = dataMain.copy(userId = PRIVATE_PROFILE)
whenever(smartspaceData.targetId).thenReturn(SMARTSPACE_KEY)
whenever(smartspaceData.isActive).thenReturn(true)
@@ -130,8 +133,19 @@ class MediaDataFilterTest : SysuiTestCase() {
private fun setUser(id: Int) {
whenever(lockscreenUserManager.isCurrentProfile(anyInt())).thenReturn(false)
+ whenever(lockscreenUserManager.isProfileAvailable(anyInt())).thenReturn(false)
whenever(lockscreenUserManager.isCurrentProfile(eq(id))).thenReturn(true)
- mediaDataFilter.handleUserSwitched(id)
+ whenever(lockscreenUserManager.isProfileAvailable(eq(id))).thenReturn(true)
+ whenever(lockscreenUserManager.isProfileAvailable(eq(PRIVATE_PROFILE))).thenReturn(true)
+ mediaDataFilter.handleUserSwitched()
+ }
+
+ private fun setPrivateProfileUnavailable() {
+ whenever(lockscreenUserManager.isCurrentProfile(anyInt())).thenReturn(false)
+ whenever(lockscreenUserManager.isCurrentProfile(eq(USER_MAIN))).thenReturn(true)
+ whenever(lockscreenUserManager.isCurrentProfile(eq(PRIVATE_PROFILE))).thenReturn(true)
+ whenever(lockscreenUserManager.isProfileAvailable(eq(PRIVATE_PROFILE))).thenReturn(false)
+ mediaDataFilter.handleProfileChanged()
}
@Test
@@ -206,6 +220,20 @@ class MediaDataFilterTest : SysuiTestCase() {
}
@Test
+ fun testOnProfileChanged_profileUnavailable_loadControls() {
+ // GIVEN that we had some media for both profiles
+ mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
+ mediaDataFilter.onMediaDataLoaded(KEY_ALT, null, dataPrivateProfile)
+ reset(listener)
+
+ // and we change profile status
+ setPrivateProfileUnavailable()
+
+ // THEN we should add the private profile media
+ verify(listener).onMediaDataRemoved(eq(KEY_ALT))
+ }
+
+ @Test
fun hasAnyMedia_noMediaSet_returnsFalse() {
assertThat(mediaDataFilter.hasAnyMedia()).isFalse()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt
index bcbf666fd302..16c92ecc2181 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt
@@ -18,38 +18,27 @@ package com.android.systemui.mediaprojection.taskswitcher
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_PSS_TASK_SWITCHER
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.mediaprojection.taskswitcher.ui.TaskSwitcherNotificationCoordinator
-import com.android.systemui.util.mockito.whenever
-import org.junit.Before
+import com.android.systemui.util.mockito.mock
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@SmallTest
class MediaProjectionTaskSwitcherCoreStartableTest : SysuiTestCase() {
- @Mock private lateinit var flags: FeatureFlags
- @Mock private lateinit var coordinator: TaskSwitcherNotificationCoordinator
+ private val coordinator = mock<TaskSwitcherNotificationCoordinator>()
- private lateinit var coreStartable: MediaProjectionTaskSwitcherCoreStartable
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- coreStartable = MediaProjectionTaskSwitcherCoreStartable(coordinator, flags)
- }
+ private val coreStartable =
+ MediaProjectionTaskSwitcherCoreStartable(notificationCoordinatorLazy = { coordinator })
@Test
fun start_flagEnabled_startsCoordinator() {
- whenever(flags.isEnabled(Flags.PARTIAL_SCREEN_SHARING_TASK_SWITCHER)).thenReturn(true)
+ mSetFlagsRule.enableFlags(FLAG_PSS_TASK_SWITCHER)
coreStartable.start()
@@ -58,7 +47,7 @@ class MediaProjectionTaskSwitcherCoreStartableTest : SysuiTestCase() {
@Test
fun start_flagDisabled_doesNotStartCoordinator() {
- whenever(flags.isEnabled(Flags.PARTIAL_SCREEN_SHARING_TASK_SWITCHER)).thenReturn(false)
+ mSetFlagsRule.disableFlags(FLAG_PSS_TASK_SWITCHER)
coreStartable.start()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
index c108a80d0c72..273ce85f89f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
@@ -28,8 +28,8 @@ import android.widget.TextView;
import androidx.test.filters.SmallTest;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -82,7 +82,7 @@ public class RearDisplayDialogControllerTest extends SysuiTestCase {
TextView deviceClosedTitleTextView = controller.mRearDisplayEducationDialog.findViewById(
R.id.rear_display_title_text_view);
- controller.onConfigurationChanged(new Configuration());
+ controller.onConfigChanged(new Configuration());
assertTrue(controller.mRearDisplayEducationDialog.isShowing());
TextView deviceClosedTitleTextView2 = controller.mRearDisplayEducationDialog.findViewById(
R.id.rear_display_title_text_view);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
index c32d2597bd61..032ec7440923 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
@@ -177,7 +177,7 @@ class UserTrackerImplTest : SysuiTestCase() {
verify(context)
.registerReceiverForAllUsers(eq(tracker), capture(captor), isNull(), eq(handler))
with(captor.value) {
- assertThat(countActions()).isEqualTo(7)
+ assertThat(countActions()).isEqualTo(11)
assertThat(hasAction(Intent.ACTION_LOCALE_CHANGED)).isTrue()
assertThat(hasAction(Intent.ACTION_USER_INFO_CHANGED)).isTrue()
assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)).isTrue()
@@ -185,6 +185,10 @@ class UserTrackerImplTest : SysuiTestCase() {
assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_ADDED)).isTrue()
assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_REMOVED)).isTrue()
assertThat(hasAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED)).isTrue()
+ assertThat(hasAction(Intent.ACTION_PROFILE_ADDED)).isTrue()
+ assertThat(hasAction(Intent.ACTION_PROFILE_REMOVED)).isTrue()
+ assertThat(hasAction(Intent.ACTION_PROFILE_AVAILABLE)).isTrue()
+ assertThat(hasAction(Intent.ACTION_PROFILE_UNAVAILABLE)).isTrue()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
index 88c728fd1b66..b94e483088f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
@@ -36,8 +36,12 @@ import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
-import dagger.Lazy
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.takeWhile
+import kotlinx.coroutines.flow.timeout
+import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.Rule
@@ -59,7 +63,6 @@ class BrightnessDialogTest : SysuiTestCase() {
@Mock private lateinit var brightnessControllerFactory: BrightnessController.Factory
@Mock private lateinit var brightnessController: BrightnessController
@Mock private lateinit var accessibilityMgr: AccessibilityManagerWrapper
- @Mock private lateinit var shadeInteractorLazy: Lazy<ShadeInteractor>
@Mock private lateinit var shadeInteractor: ShadeInteractor
private val clock = FakeSystemClock()
@@ -89,7 +92,6 @@ class BrightnessDialogTest : SysuiTestCase() {
.thenReturn(brightnessSliderController)
`when`(brightnessSliderController.rootView).thenReturn(View(context))
`when`(brightnessControllerFactory.create(any())).thenReturn(brightnessController)
- whenever(shadeInteractorLazy.get()).thenReturn(shadeInteractor)
whenever(shadeInteractor.isQsExpanded).thenReturn(MutableStateFlow(false))
}
@@ -180,6 +182,22 @@ class BrightnessDialogTest : SysuiTestCase() {
assertThat(activityRule.activity.isFinishing()).isFalse()
}
+ @OptIn(FlowPreview::class)
+ @Test
+ fun testFinishOnQSExpanded() = runTest {
+ val isQSExpanded = MutableStateFlow(false)
+ `when`(shadeInteractor.isQsExpanded).thenReturn(isQSExpanded)
+ activityRule.launchActivity(Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG))
+
+ assertThat(activityRule.activity.isFinishing()).isFalse()
+
+ isQSExpanded.value = true
+ // Observe the activity's state until is it finishing or the timeout is reached, whatever
+ // comes first. This fixes the flakiness seen when using advanceUntilIdle().
+ activityRule.activity.finishing.timeout(100.milliseconds).takeWhile { !it }.collect {}
+ assertThat(activityRule.activity.isFinishing()).isTrue()
+ }
+
class TestDialog(
brightnessSliderControllerFactory: BrightnessSliderController.Factory,
brightnessControllerFactory: BrightnessController.Factory,
@@ -194,14 +212,14 @@ class BrightnessDialogTest : SysuiTestCase() {
accessibilityMgr,
shadeInteractor
) {
- private var finishing = false
+ var finishing = MutableStateFlow(false)
override fun isFinishing(): Boolean {
- return finishing
+ return finishing.value
}
override fun requestFinish() {
- finishing = true
+ finishing.value = true
}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
index 80f8cf1559a6..50349bea390f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt
@@ -58,13 +58,13 @@ class NotificationSettingsRepositoryTest : SysuiTestCase() {
testScope.runTest {
val showNotifs by collectLastValue(underTest.isShowNotificationsOnLockScreenEnabled)
- secureSettingsRepository.set(
+ secureSettingsRepository.setInt(
name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
value = 1,
)
assertThat(showNotifs).isEqualTo(true)
- secureSettingsRepository.set(
+ secureSettingsRepository.setInt(
name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
value = 0,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
index 6eabf4412474..5e57c832074e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
@@ -17,13 +17,17 @@
package com.android.systemui.shared.plugins;
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
import androidx.test.runner.AndroidJUnit4;
@@ -40,7 +44,11 @@ import org.junit.runner.RunWith;
import java.lang.ref.WeakReference;
import java.util.Collections;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Supplier;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -104,6 +112,7 @@ public class PluginInstanceTest extends SysuiTestCase {
mPluginInstance = mPluginInstanceFactory.create(
mContext, mAppInfo, TEST_PLUGIN_COMPONENT_NAME,
TestPlugin.class, mPluginListener);
+ mPluginInstance.setIsDebug(true);
mPluginContext = new WeakReference<>(mPluginInstance.getPluginContext());
}
@@ -158,7 +167,7 @@ public class PluginInstanceTest extends SysuiTestCase {
@Test
public void testOnAttach_SkipLoad() {
- mPluginListener.mAttachReturn = false;
+ mPluginListener.mOnAttach = () -> false;
mPluginInstance.onCreate();
assertEquals(1, mPluginListener.mAttachedCount);
assertEquals(0, mPluginListener.mLoadCount);
@@ -166,6 +175,65 @@ public class PluginInstanceTest extends SysuiTestCase {
assertInstances(0, 0);
}
+ @Test
+ public void testLoadUnloadSimultaneous_HoldsUnload() throws Exception {
+ final Semaphore loadLock = new Semaphore(1);
+ final Semaphore unloadLock = new Semaphore(1);
+
+ mPluginListener.mOnAttach = () -> false;
+ mPluginListener.mOnLoad = () -> {
+ assertNotNull(mPluginInstance.getPlugin());
+
+ // Allow the bg thread the opportunity to delete the plugin
+ loadLock.release();
+ Thread.yield();
+ boolean isLocked = getLock(unloadLock, 1000);
+
+ // Ensure the bg thread failed to do delete the plugin
+ assertNotNull(mPluginInstance.getPlugin());
+ // We expect that bgThread deadlocked holding the semaphore
+ assertFalse(isLocked);
+ };
+
+ AtomicBoolean isBgThreadFailed = new AtomicBoolean(false);
+ Thread bgThread = new Thread(() -> {
+ assertTrue(getLock(unloadLock, 10));
+ assertTrue(getLock(loadLock, 3000)); // Wait for the foreground thread
+ assertNotNull(mPluginInstance.getPlugin());
+ // Attempt to delete the plugin, this should block until the load completes
+ mPluginInstance.unloadPlugin();
+ assertNull(mPluginInstance.getPlugin());
+ unloadLock.release();
+ loadLock.release();
+ });
+
+ // This protects the test suite from crashing due to the uncaught exception.
+ bgThread.setUncaughtExceptionHandler((Thread t, Throwable ex) -> {
+ Log.e("testLoadUnloadSimultaneous_HoldsUnload", "Exception from BG Thread", ex);
+ isBgThreadFailed.set(true);
+ });
+
+ loadLock.acquire();
+ mPluginInstance.onCreate();
+
+ assertNull(mPluginInstance.getPlugin());
+ bgThread.start();
+ mPluginInstance.loadPlugin();
+
+ bgThread.join(5000);
+ assertFalse(isBgThreadFailed.get());
+ assertNull(mPluginInstance.getPlugin());
+ }
+
+ private boolean getLock(Semaphore lock, long millis) {
+ try {
+ return lock.tryAcquire(millis, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException ex) {
+ fail();
+ return false;
+ }
+ }
+
// This target class doesn't matter, it just needs to have a Requires to hit the flow where
// the mock version info is called.
@ProvidesInterface(action = TestPlugin.ACTION, version = TestPlugin.VERSION)
@@ -226,7 +294,10 @@ public class PluginInstanceTest extends SysuiTestCase {
}
public class FakeListener implements PluginListener<TestPlugin> {
- public boolean mAttachReturn = true;
+ public Supplier<Boolean> mOnAttach = null;
+ public Runnable mOnDetach = null;
+ public Runnable mOnLoad = null;
+ public Runnable mOnUnload = null;
public int mAttachedCount = 0;
public int mDetachedCount = 0;
public int mLoadCount = 0;
@@ -236,13 +307,16 @@ public class PluginInstanceTest extends SysuiTestCase {
public boolean onPluginAttached(PluginLifecycleManager<TestPlugin> manager) {
mAttachedCount++;
assertEquals(PluginInstanceTest.this.mPluginInstance, manager);
- return mAttachReturn;
+ return mOnAttach != null ? mOnAttach.get() : true;
}
@Override
public void onPluginDetached(PluginLifecycleManager<TestPlugin> manager) {
mDetachedCount++;
assertEquals(PluginInstanceTest.this.mPluginInstance, manager);
+ if (mOnDetach != null) {
+ mOnDetach.run();
+ }
}
@Override
@@ -261,6 +335,9 @@ public class PluginInstanceTest extends SysuiTestCase {
assertEquals(expectedContext, pluginContext);
}
assertEquals(PluginInstanceTest.this.mPluginInstance, manager);
+ if (mOnLoad != null) {
+ mOnLoad.run();
+ }
}
@Override
@@ -274,6 +351,9 @@ public class PluginInstanceTest extends SysuiTestCase {
assertEquals(expectedPlugin, plugin);
}
assertEquals(PluginInstanceTest.this.mPluginInstance, manager);
+ if (mOnUnload != null) {
+ mOnUnload.run();
+ }
}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
index 2bee7b85eb98..d3febf55117b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
@@ -41,6 +41,7 @@ import com.android.systemui.plugins.PluginManager;
import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository;
import com.android.systemui.statusbar.domain.interactor.SilentNotificationStatusIconsVisibilityInteractor;
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -151,6 +152,7 @@ public class NotificationListenerTest extends SysuiTestCase {
@Test
public void testOnConnectReadStatusBarSetting() {
+ mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME);
NotificationListener.NotificationSettingsListener settingsListener =
mock(NotificationListener.NotificationSettingsListener.class);
mListener.addNotificationSettingsListener(settingsListener);
@@ -164,6 +166,7 @@ public class NotificationListenerTest extends SysuiTestCase {
@Test
public void testOnStatusBarIconsBehaviorChanged() {
+ mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME);
NotificationListener.NotificationSettingsListener settingsListener =
mock(NotificationListener.NotificationSettingsListener.class);
mListener.addNotificationSettingsListener(settingsListener);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index 2df6e46d630f..d4300f08fbac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -17,6 +17,7 @@ import com.android.systemui.statusbar.NotificationShelf
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.StackScrollAlgorithmState
import com.android.systemui.util.mockito.mock
import junit.framework.Assert.assertEquals
@@ -71,6 +72,7 @@ open class NotificationShelfTest : SysuiTestCase() {
@Test
fun testShadeWidth_BasedOnFractionToShade() {
+ mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME)
setFractionToShade(0f)
setOnLockscreen(true)
@@ -86,6 +88,7 @@ open class NotificationShelfTest : SysuiTestCase() {
@Test
fun testShelfIsLong_WhenNotOnLockscreen() {
+ mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME)
setFractionToShade(0f)
setOnLockscreen(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 755897442eb6..1236fcf7f3d0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -38,6 +38,7 @@ import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
import android.metrics.LogMaker;
+import android.platform.test.annotations.DisableFlags;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
@@ -84,6 +85,7 @@ import com.android.systemui.statusbar.notification.collection.render.SectionHead
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -218,6 +220,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testUpdateEmptyShadeView_notificationsVisible_zenHiding() {
when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(true);
initController(/* viewIsAttached= */ true);
@@ -238,6 +241,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testUpdateEmptyShadeView_notificationsHidden_zenNotHiding() {
when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
initController(/* viewIsAttached= */ true);
@@ -258,6 +262,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testUpdateEmptyShadeView_splitShadeMode_alwaysShowEmptyView() {
when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
initController(/* viewIsAttached= */ true);
@@ -285,6 +290,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testUpdateEmptyShadeView_bouncerShowing_flagOff_hideEmptyView() {
when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
initController(/* viewIsAttached= */ true);
@@ -306,6 +312,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testUpdateEmptyShadeView_bouncerShowing_flagOn_hideEmptyView() {
when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
initController(/* viewIsAttached= */ true);
@@ -327,6 +334,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testUpdateEmptyShadeView_bouncerNotShowing_flagOff_showEmptyView() {
when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
initController(/* viewIsAttached= */ true);
@@ -348,6 +356,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testUpdateEmptyShadeView_bouncerNotShowing_flagOn_showEmptyView() {
when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
initController(/* viewIsAttached= */ true);
@@ -504,6 +513,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testSetNotifStats_updatesHasFilteredOutSeenNotifications() {
initController(/* viewIsAttached= */ true);
mSeenNotificationsInteractor.setHasFilteredOutSeenNotifications(true);
@@ -545,6 +555,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void updateImportantForAccessibility_noChild_onKeyGuard_notImportantForA11y() {
// GIVEN: Controller is attached, active notifications is empty,
// and mNotificationStackScrollLayout.onKeyguard() is true
@@ -561,6 +572,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void updateImportantForAccessibility_hasChild_onKeyGuard_importantForA11y() {
// GIVEN: Controller is attached, active notifications is not empty,
// and mNotificationStackScrollLayout.onKeyguard() is true
@@ -584,6 +596,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void updateImportantForAccessibility_hasChild_notOnKeyGuard_importantForA11y() {
// GIVEN: Controller is attached, active notifications is not empty,
// and mNotificationStackScrollLayout.onKeyguard() is false
@@ -607,6 +620,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void updateImportantForAccessibility_noChild_notOnKeyGuard_importantForA11y() {
// GIVEN: Controller is attached, active notifications is empty,
// and mNotificationStackScrollLayout.onKeyguard() is false
@@ -623,6 +637,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void updateEmptyShadeView_onKeyguardTransitionToAod_hidesView() {
initController(/* viewIsAttached= */ true);
mController.onKeyguardTransitionChanged(
@@ -633,6 +648,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void updateEmptyShadeView_onKeyguardOccludedTransitionToAod_hidesView() {
initController(/* viewIsAttached= */ true);
mController.onKeyguardTransitionChanged(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index ad7dee33b6d8..83ba68460aa5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -51,6 +51,8 @@ import static org.mockito.Mockito.when;
import android.graphics.Insets;
import android.graphics.Rect;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableResources;
@@ -81,6 +83,7 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -191,7 +194,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
mStackScrollerInternal.initView(getContext(), mNotificationSwipeHelper,
mNotificationStackSizeCalculator);
mStackScroller = spy(mStackScrollerInternal);
- mStackScroller.setResetUserExpandedStatesRunnable(()->{});
+ mStackScroller.setResetUserExpandedStatesRunnable(() -> {});
mStackScroller.setEmptyShadeView(mEmptyShadeView);
when(mStackScrollLayoutController.isHistoryEnabled()).thenReturn(true);
when(mStackScrollLayoutController.getNotificationRoundnessManager())
@@ -309,7 +312,9 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
public void updateEmptyView_dndSuppressing() {
when(mEmptyShadeView.willBeGone()).thenReturn(true);
- mStackScroller.updateEmptyShadeView(true, true);
+ mStackScroller.updateEmptyShadeView(/* visible = */ true,
+ /* areNotificationsHiddenInShade = */ true,
+ /* hasFilteredOutSeenNotifications = */ false);
verify(mEmptyShadeView).setText(R.string.dnd_suppressing_shade_text);
}
@@ -319,7 +324,9 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
mStackScroller.setEmptyShadeView(mEmptyShadeView);
when(mEmptyShadeView.willBeGone()).thenReturn(true);
- mStackScroller.updateEmptyShadeView(true, false);
+ mStackScroller.updateEmptyShadeView(/* visible = */ true,
+ /* areNotificationsHiddenInShade = */ false,
+ /* hasFilteredOutSeenNotifications = */ false);
verify(mEmptyShadeView).setText(R.string.empty_shade_text);
}
@@ -328,10 +335,14 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
public void updateEmptyView_noNotificationsToDndSuppressing() {
mStackScroller.setEmptyShadeView(mEmptyShadeView);
when(mEmptyShadeView.willBeGone()).thenReturn(true);
- mStackScroller.updateEmptyShadeView(true, false);
+ mStackScroller.updateEmptyShadeView(/* visible = */ true,
+ /* areNotificationsHiddenInShade = */ false,
+ /* hasFilteredOutSeenNotifications = */ false);
verify(mEmptyShadeView).setText(R.string.empty_shade_text);
- mStackScroller.updateEmptyShadeView(true, true);
+ mStackScroller.updateEmptyShadeView(/* visible = */ true,
+ /* areNotificationsHiddenInShade = */ true,
+ /* hasFilteredOutSeenNotifications = */ false);
verify(mEmptyShadeView).setText(R.string.dnd_suppressing_shade_text);
}
@@ -385,8 +396,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
mStackScroller.setExpandedHeight(100f);
}
-
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void manageNotifications_visible() {
FooterView view = mock(FooterView.class);
mStackScroller.setFooterView(view);
@@ -399,6 +410,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void clearAll_visible() {
FooterView view = mock(FooterView.class);
mStackScroller.setFooterView(view);
@@ -411,6 +423,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testInflateFooterView() {
mStackScroller.inflateFooterView();
ArgumentCaptor<FooterView> captor = ArgumentCaptor.forClass(FooterView.class);
@@ -444,7 +457,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
FooterView view = mock(FooterView.class);
mStackScroller.setFooterView(view);
mStackScroller.updateFooter();
- verify(mStackScroller).updateFooterView(false, true, true);
+ verify(mStackScroller, atLeastOnce()).updateFooterView(false, true, true);
}
@Test
@@ -459,7 +472,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
FooterView view = mock(FooterView.class);
mStackScroller.setFooterView(view);
mStackScroller.updateFooter();
- verify(mStackScroller).updateFooterView(false, false, true);
+ verify(mStackScroller, atLeastOnce()).updateFooterView(false, false, true);
}
@Test
@@ -474,7 +487,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
FooterView view = mock(FooterView.class);
mStackScroller.setFooterView(view);
mStackScroller.updateFooter();
- verify(mStackScroller).updateFooterView(true, true, true);
+ verify(mStackScroller, atLeastOnce()).updateFooterView(true, true, true);
}
@Test
@@ -490,7 +503,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
FooterView view = mock(FooterView.class);
mStackScroller.setFooterView(view);
mStackScroller.updateFooter();
- verify(mStackScroller).updateFooterView(true, true, false);
+ verify(mStackScroller, atLeastOnce()).updateFooterView(true, true, false);
}
@Test
@@ -505,7 +518,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
FooterView view = mock(FooterView.class);
mStackScroller.setFooterView(view);
mStackScroller.updateFooter();
- verify(mStackScroller).updateFooterView(false, true, true);
+ verify(mStackScroller, atLeastOnce()).updateFooterView(false, true, true);
}
@Test
@@ -521,7 +534,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
FooterView view = mock(FooterView.class);
mStackScroller.setFooterView(view);
mStackScroller.updateFooter();
- verify(mStackScroller).updateFooterView(true, false, true);
+ verify(mStackScroller, atLeastOnce()).updateFooterView(true, false, true);
}
@Test
@@ -529,7 +542,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
mStackScroller.setCurrentUserSetup(true);
// add footer
- mStackScroller.inflateFooterView();
+ FooterView view = mock(FooterView.class);
+ mStackScroller.setFooterView(view);
// add notification
ExpandableNotificationRow row = createClearableRow();
@@ -545,6 +559,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void testReInflatesFooterViews() {
when(mEmptyShadeView.getTextResource()).thenReturn(R.string.empty_shade_text);
clearInvocations(mStackScroller);
@@ -554,6 +569,16 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(FooterViewRefactor.FLAG_NAME)
+ public void testReInflatesEmptyShadeView() {
+ when(mEmptyShadeView.getTextResource()).thenReturn(R.string.empty_shade_text);
+ clearInvocations(mStackScroller);
+ mStackScroller.reinflateViews();
+ verify(mStackScroller, never()).setFooterView(any());
+ verify(mStackScroller).setEmptyShadeView(any());
+ }
+
+ @Test
public void testSetIsBeingDraggedResetsExposedMenu() {
mStackScroller.setIsBeingDragged(true);
verify(mNotificationSwipeHelper).resetExposedMenuView(true, true);
@@ -601,6 +626,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
@Test
public void testClearNotifications_clearAllInProgress() {
+ mFeatureFlags.set(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE, false);
+
ExpandableNotificationRow row = createClearableRow();
when(row.getEntry().hasFinishedInitialization()).thenReturn(true);
doReturn(true).when(mStackScroller).isVisible(row);
@@ -645,6 +672,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
@Test
public void testAddNotificationUpdatesSpeedBumpIndex() {
+ mFeatureFlags.set(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE, false);
+
// initial state calculated == 0
assertEquals(0, mStackScroller.getSpeedBumpIndex());
@@ -661,6 +690,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
@Test
public void testAddAmbientNotificationNoSpeedBumpUpdate() {
+ mFeatureFlags.set(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE, false);
+
// initial state calculated == 0
assertEquals(0, mStackScroller.getSpeedBumpIndex());
@@ -677,6 +708,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
@Test
public void testRemoveNotificationUpdatesSpeedBump() {
+ mFeatureFlags.set(Flags.ENABLE_NOTIFICATIONS_SIMULATE_SLOW_MEASURE, false);
+
// initial state calculated == 0
assertEquals(0, mStackScroller.getSpeedBumpIndex());
@@ -872,6 +905,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void hasFilteredOutSeenNotifs_updateFooter() {
mStackScroller.setCurrentUserSetup(true);
@@ -887,6 +921,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(FooterViewRefactor.FLAG_NAME)
public void hasFilteredOutSeenNotifs_updateEmptyShadeView() {
mStackScroller.setHasFilteredOutSeenNotifications(true);
mStackScroller.updateEmptyShadeView(true, false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 36a471238c8a..20020f23b2df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -43,6 +43,7 @@ import com.android.systemui.statusbar.notification.stack.domain.interactor.share
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -418,13 +419,13 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
}
@Test
- fun shadeCollpaseFadeIn() =
+ fun shadeCollapseFadeIn() =
testScope.runTest {
+ val fadeIn by collectLastValue(underTest.shadeCollpaseFadeIn)
+
// Start on lockscreen without the shade
underTest.setShadeCollapseFadeInComplete(false)
showLockscreen()
-
- val fadeIn by collectLastValue(underTest.shadeCollpaseFadeIn)
assertThat(fadeIn).isEqualTo(false)
// ... then the shade expands
@@ -440,10 +441,12 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
assertThat(fadeIn).isEqualTo(false)
}
- private suspend fun showLockscreen() {
+ private suspend fun TestScope.showLockscreen() {
shadeRepository.setLockscreenShadeExpansion(0f)
shadeRepository.setQsExpansion(0f)
+ runCurrent()
keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+ runCurrent()
keyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.AOD,
to = KeyguardState.LOCKSCREEN,
@@ -451,10 +454,12 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
)
}
- private suspend fun showLockscreenWithShadeExpanded() {
+ private suspend fun TestScope.showLockscreenWithShadeExpanded() {
shadeRepository.setLockscreenShadeExpansion(1f)
shadeRepository.setQsExpansion(0f)
+ runCurrent()
keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
+ runCurrent()
keyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.AOD,
to = KeyguardState.LOCKSCREEN,
@@ -462,10 +467,12 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
)
}
- private suspend fun showLockscreenWithQSExpanded() {
+ private suspend fun TestScope.showLockscreenWithQSExpanded() {
shadeRepository.setLockscreenShadeExpansion(0f)
shadeRepository.setQsExpansion(1f)
+ runCurrent()
keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
+ runCurrent()
keyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.AOD,
to = KeyguardState.LOCKSCREEN,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
index 62d8f7fa894d..9f4e1dd8cc4d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
@@ -23,6 +23,7 @@ import com.android.systemui.SysuiTestCase
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.notification.shared.NotificationIconContainerRefactor
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
@@ -73,6 +74,7 @@ class NotificationIconContainerTest : SysuiTestCase() {
@Test
fun calculateWidthFor_fiveIcons_widthForFourIcons() {
+ mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME)
iconContainer.setActualPaddingStart(10f)
iconContainer.setActualPaddingEnd(10f)
iconContainer.setIconSize(10)
@@ -151,7 +153,7 @@ class NotificationIconContainerTest : SysuiTestCase() {
iconContainer.addView(iconFive)
assertEquals(5, iconContainer.childCount)
- val width = iconContainer.calculateWidthFor(/* numIcons= */ 5f)
+ val width = iconContainer.calculateWidthFor(/* numIcons= */ 4f)
iconContainer.setActualLayoutWidth(width.toInt())
iconContainer.calculateIconXTranslations()
@@ -212,6 +214,7 @@ class NotificationIconContainerTest : SysuiTestCase() {
@Test
fun shouldForceOverflow_appearingAboveSpeedBump_true() {
+ mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME)
val forceOverflow =
iconContainer.shouldForceOverflow(
/* i= */ 1,
@@ -228,7 +231,7 @@ class NotificationIconContainerTest : SysuiTestCase() {
iconContainer.shouldForceOverflow(
/* i= */ 10,
/* speedBumpIndex= */ 11,
- /* iconAppearAmount= */ 0f,
+ /* iconAppearAmount= */ 0.1f,
/* maxVisibleIcons= */ 5
)
assertTrue(forceOverflow)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt
index 7594c90daa8b..feff046bb708 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt
@@ -1,6 +1,7 @@
package com.android.systemui.statusbar.phone
import android.graphics.Point
+import android.testing.TestableLooper
import android.view.Display
import android.view.Surface
import android.view.View
@@ -19,6 +20,7 @@ import org.mockito.Mock
import org.mockito.MockitoAnnotations
@SmallTest
+@TestableLooper.RunWithLooper
class StatusBarMoveFromCenterAnimationControllerTest : SysuiTestCase() {
@Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
index e461e3f7fb1a..bbc96f703738 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
@@ -26,8 +26,11 @@ class TestUnfoldProgressListener : UnfoldTransitionProgressProvider.TransitionPr
private val recordings: MutableList<UnfoldTransitionRecording> = arrayListOf()
private var currentRecording: UnfoldTransitionRecording? = null
+ var lastCallbackThread: Thread? = null
+ private set
override fun onTransitionStarted() {
+ lastCallbackThread = Thread.currentThread()
assertWithMessage("Trying to start a transition when it is already in progress")
.that(currentRecording)
.isNull()
@@ -36,6 +39,7 @@ class TestUnfoldProgressListener : UnfoldTransitionProgressProvider.TransitionPr
}
override fun onTransitionProgress(progress: Float) {
+ lastCallbackThread = Thread.currentThread()
assertWithMessage("Received transition progress event when it's not started")
.that(currentRecording)
.isNotNull()
@@ -43,6 +47,7 @@ class TestUnfoldProgressListener : UnfoldTransitionProgressProvider.TransitionPr
}
override fun onTransitionFinishing() {
+ lastCallbackThread = Thread.currentThread()
assertWithMessage("Received transition finishing event when it's not started")
.that(currentRecording)
.isNotNull()
@@ -50,6 +55,7 @@ class TestUnfoldProgressListener : UnfoldTransitionProgressProvider.TransitionPr
}
override fun onTransitionFinished() {
+ lastCallbackThread = Thread.currentThread()
assertWithMessage("Received transition finish event when it's not started")
.that(currentRecording)
.isNotNull()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt
index a25469bfc09b..d864d53fea32 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.unfold.util
import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
import android.view.Surface
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -37,6 +38,7 @@ import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@SmallTest
+@TestableLooper.RunWithLooper
class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() {
@Mock lateinit var rotationChangeProvider: RotationChangeProvider
@@ -48,10 +50,12 @@ class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() {
@Captor private lateinit var rotationListenerCaptor: ArgumentCaptor<RotationListener>
lateinit var progressProvider: NaturalRotationUnfoldProgressProvider
+ private lateinit var testableLooper : TestableLooper
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ testableLooper = TestableLooper.get(this)
progressProvider =
NaturalRotationUnfoldProgressProvider(context, rotationChangeProvider, sourceProvider)
@@ -123,5 +127,6 @@ class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() {
private fun onRotationChanged(rotation: Int) {
rotationListenerCaptor.value.onRotationChanged(rotation)
+ testableLooper.processAllMessages()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
index e1e54a903917..2f29b3bdd3b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
@@ -19,6 +19,7 @@ import android.content.ContentResolver
import android.database.ContentObserver
import android.provider.Settings
import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.unfold.TestUnfoldTransitionProvider
@@ -36,6 +37,7 @@ import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@SmallTest
+@TestableLooper.RunWithLooper
class ScaleAwareUnfoldProgressProviderTest : SysuiTestCase() {
@Mock lateinit var sinkProvider: TransitionProgressListener
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt
new file mode 100644
index 000000000000..5b4f4d37214f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt
@@ -0,0 +1,161 @@
+/*
+ * 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.unfold.util
+
+import android.os.Handler
+import android.os.HandlerThread
+import android.os.Process
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.unfold.TestUnfoldTransitionProvider
+import com.android.systemui.unfold.progress.TestUnfoldProgressListener
+import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.CancellableContinuation
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.withTimeout
+import org.junit.Assert.assertThrows
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@RunWithLooper
+class ScopedUnfoldTransitionProgressProviderTest : SysuiTestCase() {
+
+ private val rootProvider = TestUnfoldTransitionProvider()
+ private val listener = TestUnfoldProgressListener()
+ private val testScope = TestScope(UnconfinedTestDispatcher())
+ private val bgThread =
+ HandlerThread("UnfoldBgTest", Process.THREAD_PRIORITY_FOREGROUND).apply { start() }
+ private val bgHandler = Handler(bgThread.looper)
+ private val scopedProvider =
+ ScopedUnfoldTransitionProgressProvider(rootProvider).apply { addCallback(listener) }
+
+ @Test
+ fun setReadyToHandleTransition_whileTransitionRunning_propagatesCallbacks() =
+ testScope.runTest {
+ runBlockingInBg { rootProvider.onTransitionStarted() }
+
+ scopedProvider.setReadyToHandleTransition(true)
+
+ runBlockingInBg { /* sync barrier */}
+
+ listener.assertStarted()
+
+ runBlockingInBg { rootProvider.onTransitionProgress(1f) }
+
+ listener.assertLastProgress(1f)
+
+ runBlockingInBg { rootProvider.onTransitionFinished() }
+
+ listener.assertNotStarted()
+ }
+
+ @Test
+ fun setReadyToHandleTransition_whileTransitionNotRunning_callbacksInProgressThread() {
+ testScope.runTest {
+ scopedProvider.setReadyToHandleTransition(true)
+
+ val bgThread = runBlockingInBg { Thread.currentThread() }
+
+ runBlockingInBg { rootProvider.onTransitionStarted() }
+
+ listener.assertStarted()
+
+ assertThat(listener.lastCallbackThread).isEqualTo(bgThread)
+ }
+ }
+
+ @Test
+ fun setReadyToHandleTransition_beforeAnyCallback_doesNotCrash() {
+ testScope.runTest { scopedProvider.setReadyToHandleTransition(true) }
+ }
+
+ @Test
+ fun onTransitionStarted_whileNotReadyToHandleTransition_doesNotPropagate() {
+ testScope.runTest {
+ scopedProvider.setReadyToHandleTransition(false)
+
+ rootProvider.onTransitionStarted()
+
+ listener.assertNotStarted()
+ }
+ }
+
+ @Test
+ fun onTransitionStarted_defaultReadiness_doesNotPropagate() {
+ testScope.runTest {
+ rootProvider.onTransitionStarted()
+
+ listener.assertNotStarted()
+ }
+ }
+
+ @Test
+ fun onTransitionStarted_fromDifferentThreads_throws() {
+ testScope.runTest {
+ runBlockingInBg {
+ rootProvider.onTransitionStarted()
+ rootProvider.onTransitionFinished()
+ }
+ assertThrows(IllegalStateException::class.java) { rootProvider.onTransitionStarted() }
+ }
+ }
+
+ @Test
+ fun onTransitionProgress_fromDifferentThreads_throws() {
+ testScope.runTest {
+ runBlockingInBg { rootProvider.onTransitionStarted() }
+ assertThrows(IllegalStateException::class.java) {
+ rootProvider.onTransitionProgress(1f)
+ }
+ }
+ }
+
+ @Test
+ fun onTransitionFinished_fromDifferentThreads_throws() {
+ testScope.runTest {
+ runBlockingInBg { rootProvider.onTransitionStarted() }
+ assertThrows(IllegalStateException::class.java) { rootProvider.onTransitionFinished() }
+ }
+ }
+
+ @Test
+ fun onTransitionFinishing_fromDifferentThreads_throws() {
+ testScope.runTest {
+ runBlockingInBg { rootProvider.onTransitionStarted() }
+ assertThrows(IllegalStateException::class.java) { rootProvider.onTransitionFinishing() }
+ }
+ }
+
+ private fun <T> runBlockingInBg(f: () -> T): T {
+ return runBlocking {
+ withTimeout(5.seconds) {
+ suspendCancellableCoroutine { c: CancellableContinuation<T> ->
+ bgHandler.post { c.resumeWith(Result.success(f())) }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt
index 4a38fc069d9f..f484ea04bb4f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.unfold.util
import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.unfold.TestUnfoldTransitionProvider
@@ -27,6 +28,7 @@ import org.junit.runner.RunWith
@RunWith(AndroidTestingRunner::class)
@SmallTest
+@TestableLooper.RunWithLooper
class UnfoldOnlyProgressProviderTest : SysuiTestCase() {
private val listener = TestUnfoldProgressListener()
@@ -54,9 +56,7 @@ class UnfoldOnlyProgressProviderTest : SysuiTestCase() {
sourceProvider.onTransitionProgress(0.5f)
sourceProvider.onTransitionFinished()
- with(listener.ensureTransitionFinished()) {
- assertLastProgress(0.5f)
- }
+ with(listener.ensureTransitionFinished()) { assertLastProgress(0.5f) }
}
@Test
@@ -121,8 +121,6 @@ class UnfoldOnlyProgressProviderTest : SysuiTestCase() {
sourceProvider.onTransitionProgress(0.1f)
sourceProvider.onTransitionFinished()
- with(listener.ensureTransitionFinished()) {
- assertLastProgress(0.1f)
- }
+ with(listener.ensureTransitionFinished()) { assertLastProgress(0.1f) }
}
}
diff --git a/packages/SystemUI/unfold/Android.bp b/packages/SystemUI/unfold/Android.bp
index 81fd8ce12f05..e52cefb2d7e4 100644
--- a/packages/SystemUI/unfold/Android.bp
+++ b/packages/SystemUI/unfold/Android.bp
@@ -39,4 +39,7 @@ android_library {
sdk_version: "current",
min_sdk_version: "current",
plugins: ["dagger2-compiler"],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
index f9751d9c279c..2bca2722be6c 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
@@ -15,8 +15,11 @@
*/
package com.android.systemui.unfold.util
+import android.os.Handler
+import android.os.Looper
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import java.util.concurrent.CopyOnWriteArrayList
/**
* Manages progress listeners that can have smaller lifespan than the unfold animation.
@@ -33,12 +36,13 @@ open class ScopedUnfoldTransitionProgressProvider
constructor(source: UnfoldTransitionProgressProvider? = null) :
UnfoldTransitionProgressProvider, TransitionProgressListener {
+ private var progressHandler: Handler? = null
private var source: UnfoldTransitionProgressProvider? = null
- private val listeners: MutableList<TransitionProgressListener> = mutableListOf()
+ private val listeners = CopyOnWriteArrayList<TransitionProgressListener>()
- private var isReadyToHandleTransition = false
- private var isTransitionRunning = false
+ @Volatile private var isReadyToHandleTransition = false
+ @Volatile private var isTransitionRunning = false
private var lastTransitionProgress = PROGRESS_UNSET
init {
@@ -70,15 +74,18 @@ constructor(source: UnfoldTransitionProgressProvider? = null) :
* Call it with readyToHandleTransition = false when listeners can't process the events.
*/
fun setReadyToHandleTransition(isReadyToHandleTransition: Boolean) {
- if (isTransitionRunning) {
- if (isReadyToHandleTransition) {
- listeners.forEach { it.onTransitionStarted() }
- if (lastTransitionProgress != PROGRESS_UNSET) {
- listeners.forEach { it.onTransitionProgress(lastTransitionProgress) }
+ val progressHandler = this.progressHandler
+ if (isTransitionRunning && progressHandler != null) {
+ progressHandler.post {
+ if (isReadyToHandleTransition) {
+ listeners.forEach { it.onTransitionStarted() }
+ if (lastTransitionProgress != PROGRESS_UNSET) {
+ listeners.forEach { it.onTransitionProgress(lastTransitionProgress) }
+ }
+ } else {
+ isTransitionRunning = false
+ listeners.forEach { it.onTransitionFinished() }
}
- } else {
- isTransitionRunning = false
- listeners.forEach { it.onTransitionFinished() }
}
}
this.isReadyToHandleTransition = isReadyToHandleTransition
@@ -98,6 +105,7 @@ constructor(source: UnfoldTransitionProgressProvider? = null) :
}
override fun onTransitionStarted() {
+ assertInProgressThread()
isTransitionRunning = true
if (isReadyToHandleTransition) {
listeners.forEach { it.onTransitionStarted() }
@@ -105,6 +113,7 @@ constructor(source: UnfoldTransitionProgressProvider? = null) :
}
override fun onTransitionProgress(progress: Float) {
+ assertInProgressThread()
if (isReadyToHandleTransition) {
listeners.forEach { it.onTransitionProgress(progress) }
}
@@ -112,12 +121,14 @@ constructor(source: UnfoldTransitionProgressProvider? = null) :
}
override fun onTransitionFinishing() {
+ assertInProgressThread()
if (isReadyToHandleTransition) {
listeners.forEach { it.onTransitionFinishing() }
}
}
override fun onTransitionFinished() {
+ assertInProgressThread()
if (isReadyToHandleTransition) {
listeners.forEach { it.onTransitionFinished() }
}
@@ -125,6 +136,21 @@ constructor(source: UnfoldTransitionProgressProvider? = null) :
lastTransitionProgress = PROGRESS_UNSET
}
+ private fun assertInProgressThread() {
+ val cachedProgressHandler = progressHandler
+ if (cachedProgressHandler == null) {
+ val thisLooper = Looper.myLooper() ?: error("This thread is expected to have a looper.")
+ progressHandler = Handler(thisLooper)
+ } else {
+ check(cachedProgressHandler.looper.isCurrentThread) {
+ """Receiving unfold transition callback from different threads.
+ |Current: ${Thread.currentThread()}
+ |expected: ${cachedProgressHandler.looper.thread}"""
+ .trimMargin()
+ }
+ }
+ }
+
companion object {
private const val PROGRESS_UNSET = -1f
}
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index 7744fcaf032a..491ed22ce2c8 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -55,6 +55,7 @@ android.os.PackageTagsList
android.os.Parcel
android.os.Parcelable
android.os.Process
+android.os.ServiceSpecificException
android.os.SystemClock
android.os.ThreadLocalWorkSource
android.os.TimestampedValue
diff --git a/services/backup/Android.bp b/services/backup/Android.bp
index acb5911c8868..d08a97e2a573 100644
--- a/services/backup/Android.bp
+++ b/services/backup/Android.bp
@@ -19,7 +19,13 @@ java_library_static {
defaults: ["platform_service_defaults"],
srcs: [":services.backup-sources"],
libs: ["services.core"],
- static_libs: ["app-compat-annotations", "backup_flags_lib"],
+ static_libs: [
+ "app-compat-annotations",
+ "backup_flags_lib",
+ ],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
aconfig_declarations {
diff --git a/services/companion/Android.bp b/services/companion/Android.bp
index 550e17be276d..2bfdd0a7c819 100644
--- a/services/companion/Android.bp
+++ b/services/companion/Android.bp
@@ -31,4 +31,7 @@ java_library_static {
"virtualdevice_flags_lib",
"virtual_camera_service_aidl-java",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/services/companion/java/com/android/server/companion/PackageUtils.java b/services/companion/java/com/android/server/companion/PackageUtils.java
index db40fc495cab..6c77018de27b 100644
--- a/services/companion/java/com/android/server/companion/PackageUtils.java
+++ b/services/companion/java/com/android/server/companion/PackageUtils.java
@@ -19,6 +19,7 @@ package com.android.server.companion;
import static android.content.pm.PackageManager.FEATURE_COMPANION_DEVICE_SETUP;
import static android.content.pm.PackageManager.GET_CONFIGURATIONS;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
+import static android.os.Binder.getCallingUid;
import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
import static com.android.server.companion.CompanionDeviceManagerService.TAG;
@@ -41,6 +42,7 @@ import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.Signature;
import android.os.Binder;
+import android.os.Process;
import android.util.Log;
import android.util.Slog;
@@ -80,6 +82,11 @@ public final class PackageUtils {
static void enforceUsesCompanionDeviceFeature(@NonNull Context context,
@UserIdInt int userId, @NonNull String packageName) {
+ // Allow system server to create CDM associations without FEATURE_COMPANION_DEVICE_SETUP
+ if (getCallingUid() == Process.SYSTEM_UID) {
+ return;
+ }
+
String requiredFeature = FEATURE_COMPANION_DEVICE_SETUP;
FeatureInfo[] requestedFeatures = getPackageInfo(context, userId, packageName).reqFeatures;
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 5111b08a1812..dd001ec7da27 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -212,6 +212,9 @@ java_library_static {
"-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
"-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
java_genrule {
@@ -230,6 +233,9 @@ java_genrule {
java_library {
name: "services.core",
static_libs: ["services.core.priorityboosted"],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
java_library_host {
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index cac2efba1c89..08093c0c037f 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -1463,4 +1463,9 @@ public abstract class PackageManagerInternal {
*/
@NonNull
public abstract PackageArchiver getPackageArchiver();
+
+ /**
+ * Returns true if the device is upgrading from an SDK version lower than the one specified.
+ */
+ public abstract boolean isUpgradingFromLowerThan(int sdkVersion);
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 3ae5527153b7..96b1650d9575 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -303,6 +303,23 @@ public final class ActiveServices {
@Retention(RetentionPolicy.SOURCE)
@interface FgsStopReason {}
+ /**
+ * Disables foreground service background starts from BOOT_COMPLETED broadcasts for all types
+ * except:
+ * <ul>
+ * <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_LOCATION}</li>
+ * <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE}</li>
+ * <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING}</li>
+ * <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_HEALTH}</li>
+ * <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED}</li>
+ * <li>{@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SPECIAL_USE}</li>
+ * </ul>
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = VERSION_CODES.VANILLA_ICE_CREAM)
+ @Overridable
+ public static final long FGS_BOOT_COMPLETED_RESTRICTIONS = 296558535L;
+
final ActivityManagerService mAm;
// Maximum number of services that we allow to start in the background
@@ -1053,6 +1070,20 @@ public final class ActiveServices {
}
}
+ private boolean shouldAllowBootCompletedStart(ServiceRecord r, int foregroundServiceType) {
+ @PowerExemptionManager.ReasonCode final int fgsStartReasonCode =
+ r.mInfoTempFgsAllowListReason != null ? r.mInfoTempFgsAllowListReason.mReasonCode
+ : REASON_DENIED;
+ if (Flags.fgsBootCompleted()
+ && CompatChanges.isChangeEnabled(FGS_BOOT_COMPLETED_RESTRICTIONS, r.appInfo.uid)
+ && fgsStartReasonCode == PowerExemptionManager.REASON_BOOT_COMPLETED) {
+ // Filter through types
+ return ((foregroundServiceType & mAm.mConstants.FGS_BOOT_COMPLETED_ALLOWLIST) != 0);
+ }
+ // Not BOOT_COMPLETED
+ return true;
+ }
+
private ComponentName startServiceInnerLocked(ServiceRecord r, Intent service,
int callingUid, int callingPid, String callingProcessName,
int callingProcessState, boolean fgRequired, boolean callerFg,
@@ -2087,6 +2118,11 @@ public final class ActiveServices {
// anyway, so we just remove the SHORT_SERVICE type.
foregroundServiceType &= ~FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
}
+ if (!shouldAllowBootCompletedStart(r, foregroundServiceType)) {
+ throw new ForegroundServiceStartNotAllowedException("FGS type "
+ + ServiceInfo.foregroundServiceTypeToLabel(foregroundServiceType)
+ + " not allowed to start from BOOT_COMPLETED!");
+ }
boolean alreadyStartedOp = false;
boolean stopProcStatsOp = false;
@@ -8303,7 +8339,7 @@ public final class ActiveServices {
r.mFgsNotificationShown,
durationMs,
r.mStartForegroundCount,
- ActivityManagerUtils.hashComponentNameForAtom(r.shortInstanceName),
+ 0, // Short instance name -- no longer logging it.
r.mFgsHasNotificationPermission,
r.foregroundServiceType,
fgsTypeCheckCode,
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 1d699057e353..72e62c37106d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -16,6 +16,12 @@
package com.android.server.am;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_HEALTH;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED;
import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED;
import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_NONE;
@@ -73,6 +79,9 @@ final class ActivityManagerConstants extends ContentObserver {
= "fgservice_screen_on_before_time";
private static final String KEY_FGSERVICE_SCREEN_ON_AFTER_TIME
= "fgservice_screen_on_after_time";
+
+ private static final String KEY_FGS_BOOT_COMPLETED_ALLOWLIST = "fgs_boot_completed_allowlist";
+
private static final String KEY_CONTENT_PROVIDER_RETAIN_TIME = "content_provider_retain_time";
private static final String KEY_GC_TIMEOUT = "gc_timeout";
private static final String KEY_GC_MIN_INTERVAL = "gc_min_interval";
@@ -166,6 +175,15 @@ final class ActivityManagerConstants extends ContentObserver {
private static final long DEFAULT_FGSERVICE_MIN_REPORT_TIME = 3*1000;
private static final long DEFAULT_FGSERVICE_SCREEN_ON_BEFORE_TIME = 1*1000;
private static final long DEFAULT_FGSERVICE_SCREEN_ON_AFTER_TIME = 5*1000;
+
+ private static final int DEFAULT_FGS_BOOT_COMPLETED_ALLOWLIST =
+ FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE
+ | FOREGROUND_SERVICE_TYPE_HEALTH
+ | FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING
+ | FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED
+ | FOREGROUND_SERVICE_TYPE_SPECIAL_USE
+ | FOREGROUND_SERVICE_TYPE_LOCATION;
+
private static final long DEFAULT_CONTENT_PROVIDER_RETAIN_TIME = 20*1000;
private static final long DEFAULT_GC_TIMEOUT = 5*1000;
private static final long DEFAULT_GC_MIN_INTERVAL = 60*1000;
@@ -446,6 +464,9 @@ final class ActivityManagerConstants extends ContentObserver {
// on until we will stop reporting it.
public long FGSERVICE_SCREEN_ON_AFTER_TIME = DEFAULT_FGSERVICE_SCREEN_ON_AFTER_TIME;
+ // Allow-list for FGS types that are allowed to start from BOOT_COMPLETED.
+ public int FGS_BOOT_COMPLETED_ALLOWLIST = DEFAULT_FGS_BOOT_COMPLETED_ALLOWLIST;
+
// How long we will retain processes hosting content providers in the "last activity"
// state before allowing them to drop down to the regular cached LRU list. This is
// to avoid thrashing of provider processes under low memory situations.
@@ -629,6 +650,10 @@ final class ActivityManagerConstants extends ContentObserver {
// foreground service background start restriction.
volatile boolean mFgsStartRestrictionNotificationEnabled = false;
+ // Indicates whether PSS profiling in AppProfiler is force-enabled, even if RSS is used by
+ // default. Controlled by Settings.Global.FORCE_ENABLE_PSS_PROFILING
+ volatile boolean mForceEnablePssProfiling = false;
+
/**
* Indicates whether the foreground service background start restriction is enabled for
* caller app that is targeting S+.
@@ -958,6 +983,9 @@ final class ActivityManagerConstants extends ContentObserver {
private static final Uri ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS_URI =
Settings.Global.getUriFor(Settings.Global.ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS);
+ private static final Uri FORCE_ENABLE_PSS_PROFILING_URI =
+ Settings.Global.getUriFor(Settings.Global.FORCE_ENABLE_PSS_PROFILING);
+
/**
* The threshold to decide if a given association should be dumped into metrics.
*/
@@ -1368,6 +1396,7 @@ final class ActivityManagerConstants extends ContentObserver {
mResolver.registerContentObserver(ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS_URI,
false, this);
}
+ mResolver.registerContentObserver(FORCE_ENABLE_PSS_PROFILING_URI, false, this);
updateConstants();
if (mSystemServerAutomaticHeapDumpEnabled) {
updateEnableAutomaticSystemServerHeapDumps();
@@ -1383,6 +1412,7 @@ final class ActivityManagerConstants extends ContentObserver {
// The following read from Settings.
updateActivityStartsLoggingEnabled();
updateForegroundServiceStartsLoggingEnabled();
+ updateForceEnablePssProfiling();
// Read DropboxRateLimiter params from flags.
mService.initDropboxRateLimiter();
}
@@ -1424,6 +1454,8 @@ final class ActivityManagerConstants extends ContentObserver {
updateForegroundServiceStartsLoggingEnabled();
} else if (ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS_URI.equals(uri)) {
updateEnableAutomaticSystemServerHeapDumps();
+ } else if (FORCE_ENABLE_PSS_PROFILING_URI.equals(uri)) {
+ updateForceEnablePssProfiling();
}
}
@@ -1450,6 +1482,8 @@ final class ActivityManagerConstants extends ContentObserver {
DEFAULT_FGSERVICE_SCREEN_ON_BEFORE_TIME);
FGSERVICE_SCREEN_ON_AFTER_TIME = mParser.getLong(KEY_FGSERVICE_SCREEN_ON_AFTER_TIME,
DEFAULT_FGSERVICE_SCREEN_ON_AFTER_TIME);
+ FGS_BOOT_COMPLETED_ALLOWLIST = mParser.getInt(KEY_FGS_BOOT_COMPLETED_ALLOWLIST,
+ DEFAULT_FGS_BOOT_COMPLETED_ALLOWLIST);
CONTENT_PROVIDER_RETAIN_TIME = mParser.getLong(KEY_CONTENT_PROVIDER_RETAIN_TIME,
DEFAULT_CONTENT_PROVIDER_RETAIN_TIME);
GC_TIMEOUT = mParser.getLong(KEY_GC_TIMEOUT,
@@ -1536,6 +1570,11 @@ final class ActivityManagerConstants extends ContentObserver {
Settings.Global.ACTIVITY_STARTS_LOGGING_ENABLED, 1) == 1;
}
+ private void updateForceEnablePssProfiling() {
+ mForceEnablePssProfiling = Settings.Global.getInt(mResolver,
+ Settings.Global.FORCE_ENABLE_PSS_PROFILING, 0) == 1;
+ }
+
private void updateBackgroundActivityStarts() {
mFlagBackgroundActivityStartsEnabled = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -2091,6 +2130,8 @@ final class ActivityManagerConstants extends ContentObserver {
pw.println(FGSERVICE_SCREEN_ON_BEFORE_TIME);
pw.print(" "); pw.print(KEY_FGSERVICE_SCREEN_ON_AFTER_TIME); pw.print("=");
pw.println(FGSERVICE_SCREEN_ON_AFTER_TIME);
+ pw.print(" "); pw.print(KEY_FGS_BOOT_COMPLETED_ALLOWLIST); pw.print("=");
+ pw.println(FGS_BOOT_COMPLETED_ALLOWLIST);
pw.print(" "); pw.print(KEY_CONTENT_PROVIDER_RETAIN_TIME); pw.print("=");
pw.println(CONTENT_PROVIDER_RETAIN_TIME);
pw.print(" "); pw.print(KEY_GC_TIMEOUT); pw.print("=");
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f8451fda964f..671c8e984253 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -327,7 +327,6 @@ import android.os.Debug;
import android.os.DropBoxManager;
import android.os.FactoryTest;
import android.os.FileUtils;
-import android.os.Flags;
import android.os.Handler;
import android.os.IBinder;
import android.os.IDeviceIdentifiersPolicyService;
@@ -8608,7 +8607,7 @@ public class ActivityManagerService extends IActivityManager.Stub
final long initialIdlePssOrRss, lastPssOrRss, lastSwapPss;
synchronized (mAppProfiler.mProfilerLock) {
initialIdlePssOrRss = pr.getInitialIdlePssOrRss();
- lastPssOrRss = !Flags.removeAppProfilerPssCollection()
+ lastPssOrRss = mAppProfiler.isProfilingPss()
? pr.getLastPss() : pr.getLastRss();
lastSwapPss = pr.getLastSwapPss();
}
@@ -8618,14 +8617,14 @@ public class ActivityManagerService extends IActivityManager.Stub
final StringBuilder sb2 = new StringBuilder(128);
sb2.append("Kill");
sb2.append(proc.processName);
- if (!Flags.removeAppProfilerPssCollection()) {
+ if (mAppProfiler.isProfilingPss()) {
sb2.append(" in idle maint: pss=");
} else {
sb2.append(" in idle maint: rss=");
}
sb2.append(lastPssOrRss);
- if (!Flags.removeAppProfilerPssCollection()) {
+ if (mAppProfiler.isProfilingPss()) {
sb2.append(", swapPss=");
sb2.append(lastSwapPss);
sb2.append(", initialPss=");
@@ -8640,7 +8639,7 @@ public class ActivityManagerService extends IActivityManager.Stub
Slog.wtfQuiet(TAG, sb2.toString());
mHandler.post(() -> {
synchronized (ActivityManagerService.this) {
- proc.killLocked(!Flags.removeAppProfilerPssCollection()
+ proc.killLocked(mAppProfiler.isProfilingPss()
? "idle maint (pss " : "idle maint (rss " + lastPssOrRss
+ " from " + initialIdlePssOrRss + ")",
ApplicationExitInfo.REASON_OTHER,
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 00dd169e6cc4..848a2b004f25 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -30,6 +30,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER;
import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_UNDEFINED;
import static android.content.pm.PackageManager.MATCH_ANY_USER;
+import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
import static android.os.Process.INVALID_UID;
import static android.view.Display.INVALID_DISPLAY;
import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
@@ -555,6 +556,13 @@ final class ActivityManagerShellCommand extends ShellCommand {
} else if (opt.equals("--dismiss-keyguard-if-insecure")
|| opt.equals("--dismiss-keyguard")) {
mDismissKeyguardIfInsecure = true;
+ } else if (opt.equals("--allow-fgs-start-reason")) {
+ final int reasonCode = Integer.parseInt(getNextArgRequired());
+ mBroadcastOptions = BroadcastOptions.makeBasic();
+ mBroadcastOptions.setTemporaryAppAllowlist(10_000,
+ TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
+ reasonCode,
+ "");
} else {
return false;
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerUtils.java b/services/core/java/com/android/server/am/ActivityManagerUtils.java
index 01466b845a61..78a2ecb8fba1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerUtils.java
+++ b/services/core/java/com/android/server/am/ActivityManagerUtils.java
@@ -129,14 +129,6 @@ public class ActivityManagerUtils {
}
/**
- * @param shortInstanceName {@link ServiceRecord#shortInstanceName}.
- * @return hash of the ServiceRecord's shortInstanceName, combined with ANDROID_ID.
- */
- public static int hashComponentNameForAtom(String shortInstanceName) {
- return getUnsignedHashUnCached(shortInstanceName) ^ getAndroidIdHash();
- }
-
- /**
* Helper method to log an unsafe intent event.
*/
public static void logUnsafeIntentEvent(int event, int callingUid,
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 2e0aec973bd6..e4956b348dc9 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -602,7 +602,7 @@ public class AppProfiler {
public void handleMessage(Message msg) {
switch (msg.what) {
case COLLECT_PSS_BG_MSG:
- if (!Flags.removeAppProfilerPssCollection()) {
+ if (isProfilingPss()) {
collectPssInBackground();
} else {
collectRssInBackground();
@@ -748,6 +748,11 @@ public class AppProfiler {
} while (true);
}
+ boolean isProfilingPss() {
+ return !Flags.removeAppProfilerPssCollection()
+ || mService.mConstants.mForceEnablePssProfiling;
+ }
+
// This method is analogous to collectPssInBackground() and is intended to be used as a
// replacement if Flags.removeAppProfilerPssCollection() is enabled. References to PSS in
// methods outside of AppProfiler have generally been kept where a new RSS equivalent is not
diff --git a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
index 05303f6fef63..b68572fa5c1b 100644
--- a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
+++ b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
@@ -519,7 +519,7 @@ public class ForegroundServiceTypeLoggerModule {
r.mFgsNotificationShown,
0, // durationMs
r.mStartForegroundCount,
- ActivityManagerUtils.hashComponentNameForAtom(r.shortInstanceName),
+ 0, // Short instance name -- no longer logging it.
r.mFgsHasNotificationPermission,
r.foregroundServiceType,
0,
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index b507a604343e..f49e25af79d3 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -143,7 +143,6 @@ import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.ServiceInfo;
import android.net.NetworkPolicyManager;
-import android.os.Flags;
import android.os.Handler;
import android.os.IBinder;
import android.os.PowerManagerInternal;
@@ -2418,7 +2417,7 @@ public class OomAdjuster {
// normally be a B service, but if we are low on RAM and it
// is large we want to force it down since we would prefer to
// keep launcher over it.
- long lastPssOrRss = !Flags.removeAppProfilerPssCollection()
+ long lastPssOrRss = mService.mAppProfiler.isProfilingPss()
? app.mProfile.getLastPss() : app.mProfile.getLastRss();
// RSS is larger than PSS, but the RSS/PSS ratio varies per-process based on how
@@ -2427,9 +2426,8 @@ public class OomAdjuster {
//
// TODO(b/296454553): Tune the second value so that the relative number of
// service B is similar before/after this flag is enabled.
- double thresholdModifier = !Flags.removeAppProfilerPssCollection()
- ? 1
- : mConstants.PSS_TO_RSS_THRESHOLD_MODIFIER;
+ double thresholdModifier = mService.mAppProfiler.isProfilingPss()
+ ? 1 : mConstants.PSS_TO_RSS_THRESHOLD_MODIFIER;
double cachedRestoreThreshold =
mProcessList.getCachedRestoreThresholdKb() * thresholdModifier;
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index e57206ef9bc4..b03183cb37d5 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -94,7 +94,6 @@ import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.DropBoxManager;
-import android.os.Flags;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -2482,7 +2481,6 @@ public final class ProcessList {
app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
app.info.dataDir, null, app.info.packageName,
app.getDisabledCompatChanges(),
- bindOverrideSysprops,
new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()});
} else if (hostingRecord.usesAppZygote()) {
final AppZygote appZygote = createAppZygoteForProcessIfNeeded(app);
@@ -4733,7 +4731,7 @@ public final class ProcessList {
pw.print("state: cur="); pw.print(makeProcStateString(state.getCurProcState()));
pw.print(" set="); pw.print(makeProcStateString(state.getSetProcState()));
// These values won't be collected if the flag is enabled.
- if (!Flags.removeAppProfilerPssCollection()) {
+ if (service.mAppProfiler.isProfilingPss()) {
pw.print(" lastPss=");
DebugUtils.printSizeValue(pw, r.mProfile.getLastPss() * 1024);
pw.print(" lastSwapPss=");
diff --git a/services/core/java/com/android/server/am/ProcessProfileRecord.java b/services/core/java/com/android/server/am/ProcessProfileRecord.java
index 8ca64f83ea79..d8f797c3d3e3 100644
--- a/services/core/java/com/android/server/am/ProcessProfileRecord.java
+++ b/services/core/java/com/android/server/am/ProcessProfileRecord.java
@@ -23,7 +23,6 @@ import android.app.IApplicationThread;
import android.app.ProcessMemoryState.HostingComponentType;
import android.content.pm.ApplicationInfo;
import android.os.Debug;
-import android.os.Flags;
import android.os.Process;
import android.os.SystemClock;
import android.util.DebugUtils;
@@ -677,7 +676,7 @@ final class ProcessProfileRecord {
void dumpPss(PrintWriter pw, String prefix, long nowUptime) {
synchronized (mProfilerLock) {
// TODO(b/297542292): Remove this case once PSS profiling is replaced
- if (!Flags.removeAppProfilerPssCollection()) {
+ if (mService.mAppProfiler.isProfilingPss()) {
pw.print(prefix);
pw.print("lastPssTime=");
TimeUtils.formatDuration(mLastPssTime, nowUptime, pw);
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index 5ad921fd0bae..3391ec7c4e68 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -29,7 +29,6 @@ import static com.android.server.am.ProcessRecord.TAG;
import android.annotation.ElapsedRealtimeLong;
import android.app.ActivityManager;
import android.content.ComponentName;
-import android.os.Flags;
import android.os.SystemClock;
import android.os.Trace;
import android.util.Slog;
@@ -1351,7 +1350,7 @@ final class ProcessStateRecord {
}
if (mNotCachedSinceIdle) {
pw.print(prefix); pw.print("notCachedSinceIdle="); pw.print(mNotCachedSinceIdle);
- if (!Flags.removeAppProfilerPssCollection()) {
+ if (mService.mAppProfiler.isProfilingPss()) {
pw.print(" initialIdlePss=");
} else {
pw.print(" initialIdleRss=");
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 3e1edf2a4876..d0d647c7a669 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -141,6 +141,7 @@ public class SettingsToPropertiesMapper {
"context_hub",
"core_experiments_team_internal",
"core_graphics",
+ "core_libraries",
"dck_framework",
"devoptions_settings",
"game",
@@ -178,6 +179,7 @@ public class SettingsToPropertiesMapper {
"text",
"threadnetwork",
"tv_system_ui",
+ "usb",
"vibrator",
"virtual_devices",
"wallet_integration",
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index ea791b775125..37fe38924037 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -44,6 +44,7 @@ import static com.android.internal.annotations.VisibleForTesting.Visibility.PACK
import static com.android.media.audio.Flags.alarmMinVolumeZero;
import static com.android.media.audio.Flags.bluetoothMacAddressAnonymization;
import static com.android.media.audio.Flags.disablePrescaleAbsoluteVolume;
+import static com.android.media.audio.Flags.ringerModeAffectsAlarm;
import static com.android.server.audio.SoundDoseHelper.ACTION_CHECK_MUSIC_ACTIVE;
import static com.android.server.utils.EventLogger.Event.ALOGE;
import static com.android.server.utils.EventLogger.Event.ALOGI;
@@ -607,6 +608,7 @@ public class AudioService extends IAudioService.Stub
};
private final boolean mUseFixedVolume;
+ private final boolean mRingerModeAffectsAlarm;
private final boolean mUseVolumeGroupAliases;
// If absolute volume is supported in AVRCP device
@@ -1300,6 +1302,9 @@ public class AudioService extends IAudioService.Stub
mUseFixedVolume = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_useFixedVolume);
+ mRingerModeAffectsAlarm = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_audio_ringer_mode_affects_alarm_stream);
+
mRecordMonitor = new RecordingActivityMonitor(mContext);
mRecordMonitor.registerRecordingCallback(mVoiceRecordingActivityMonitor, true);
@@ -7019,6 +7024,19 @@ public class AudioService extends IAudioService.Stub
ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_DTMF);
}
+ if (ringerModeAffectsAlarm()) {
+ if (mRingerModeAffectsAlarm) {
+ boolean muteAlarmWithRinger =
+ mSettings.getGlobalInt(mContentResolver,
+ Settings.Global.MUTE_ALARM_STREAM_WITH_RINGER_MODE,
+ /* def= */ 0) != 0;
+ if (muteAlarmWithRinger) {
+ ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_ALARM);
+ } else {
+ ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_ALARM);
+ }
+ }
+ }
if (ringerModeAffectedStreams != mRingerModeAffectedStreams) {
mSettings.putSystemIntForUser(mContentResolver,
Settings.System.MODE_RINGER_STREAMS_AFFECTED,
@@ -9678,6 +9696,8 @@ public class AudioService extends IAudioService.Stub
Settings.Global.ZEN_MODE), false, this);
mContentResolver.registerContentObserver(Settings.Global.getUriFor(
Settings.Global.ZEN_MODE_CONFIG_ETAG), false, this);
+ mContentResolver.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.MUTE_ALARM_STREAM_WITH_RINGER_MODE), false, this);
mContentResolver.registerContentObserver(Settings.System.getUriFor(
Settings.System.MODE_RINGER_STREAMS_AFFECTED), false, this);
mContentResolver.registerContentObserver(Settings.Global.getUriFor(
diff --git a/services/core/java/com/android/server/audio/FadeOutManager.java b/services/core/java/com/android/server/audio/FadeOutManager.java
index 2cceb5ab5d2e..cbcd8f5974ac 100644
--- a/services/core/java/com/android/server/audio/FadeOutManager.java
+++ b/services/core/java/com/android/server/audio/FadeOutManager.java
@@ -346,6 +346,7 @@ public final class FadeOutManager {
if (apc.getPlayerProxy() != null) {
applyVolumeShaperInternal(apc, piid, volShaper,
skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED);
+ mFadedPlayers.put(piid, volShaper);
} else {
if (DEBUG) {
Slog.v(TAG, "Error fading out player piid:" + piid
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 6af223b3660a..0f964bb75944 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -78,6 +78,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.function.Function;
import java.util.function.Supplier;
/**
@@ -99,6 +100,9 @@ public class FaceService extends SystemService {
mBiometricStateCallback;
@NonNull
private final FaceProviderFunction mFaceProviderFunction;
+ @NonNull private final Function<String, FaceProvider> mFaceProvider;
+ @NonNull
+ private final Supplier<String[]> mAidlInstanceNameSupplier;
interface FaceProviderFunction {
FaceProvider getFaceProvider(Pair<String, SensorProps[]> filteredSensorProps,
@@ -671,23 +675,9 @@ public class FaceService extends SystemService {
final List<ServiceProvider> providers = new ArrayList<>();
for (String instance : instances) {
- final String fqName = IFace.DESCRIPTOR + "/" + instance;
- final IFace face = IFace.Stub.asInterface(
- Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName)));
- if (face == null) {
- Slog.e(TAG, "Unable to get declared service: " + fqName);
- continue;
- }
- try {
- final SensorProps[] props = face.getSensorProps();
- final FaceProvider provider = new FaceProvider(getContext(),
- mBiometricStateCallback, props, instance, mLockoutResetDispatcher,
- BiometricContext.getInstance(getContext()),
- false /* resetLockoutRequiresChallenge */);
- providers.add(provider);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
- }
+ final FaceProvider provider = mFaceProvider.apply(instance);
+ Slog.i(TAG, "Adding AIDL provider: " + instance);
+ providers.add(provider);
}
return providers;
@@ -700,7 +690,7 @@ public class FaceService extends SystemService {
mRegistry.registerAll(() -> {
List<String> aidlSensors = new ArrayList<>();
- final String[] instances = ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR);
+ final String[] instances = mAidlInstanceNameSupplier.get();
if (instances != null) {
aidlSensors.addAll(Lists.newArrayList(instances));
}
@@ -813,11 +803,15 @@ public class FaceService extends SystemService {
public FaceService(Context context) {
this(context, null /* faceProviderFunction */, () -> IBiometricService.Stub.asInterface(
- ServiceManager.getService(Context.BIOMETRIC_SERVICE)));
+ ServiceManager.getService(Context.BIOMETRIC_SERVICE)), null /* faceProvider */,
+ () -> ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR));
}
- @VisibleForTesting FaceService(Context context, FaceProviderFunction faceProviderFunction,
- Supplier<IBiometricService> biometricServiceSupplier) {
+ @VisibleForTesting FaceService(Context context,
+ FaceProviderFunction faceProviderFunction,
+ Supplier<IBiometricService> biometricServiceSupplier,
+ Function<String, FaceProvider> faceProvider,
+ Supplier<String[]> aidlInstanceNameSupplier) {
super(context);
mServiceWrapper = new FaceServiceWrapper();
mLockoutResetDispatcher = new LockoutResetDispatcher(context);
@@ -830,6 +824,28 @@ public class FaceService extends SystemService {
mBiometricStateCallback.start(mRegistry.getProviders());
}
});
+ mAidlInstanceNameSupplier = aidlInstanceNameSupplier;
+
+ mFaceProvider = faceProvider != null ? faceProvider : (name) -> {
+ final String fqName = IFace.DESCRIPTOR + "/" + name;
+ final IFace face = IFace.Stub.asInterface(
+ Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName)));
+ if (face == null) {
+ Slog.e(TAG, "Unable to get declared service: " + fqName);
+ return null;
+ }
+ try {
+ final SensorProps[] props = face.getSensorProps();
+ return new FaceProvider(getContext(),
+ mBiometricStateCallback, props, name, mLockoutResetDispatcher,
+ BiometricContext.getInstance(getContext()),
+ false /* resetLockoutRequiresChallenge */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
+ }
+
+ return null;
+ };
if (Flags.deHidl()) {
mFaceProviderFunction = faceProviderFunction != null ? faceProviderFunction :
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 3024dd27b5c4..8910b6e58432 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -82,6 +82,7 @@ public class AutomaticBrightnessController {
public static final int AUTO_BRIGHTNESS_MODE_DEFAULT = 0;
public static final int AUTO_BRIGHTNESS_MODE_IDLE = 1;
public static final int AUTO_BRIGHTNESS_MODE_DOZE = 2;
+ public static final int AUTO_BRIGHTNESS_MODE_MAX = AUTO_BRIGHTNESS_MODE_DOZE;
// How long the current sensor reading is assumed to be valid beyond the current time.
// This provides a bit of prediction, as well as ensures that the weight for the last sample is
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index e38d08ff2a1a..bc3f9dd3cb8c 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -4573,8 +4573,10 @@ public final class DisplayManagerService extends SystemService {
if ((flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) {
final DisplayPowerControllerInterface displayPowerController =
mDisplayPowerControllers.get(id);
- ready &= displayPowerController.requestPowerState(request,
- waitForNegativeProximity);
+ if (displayPowerController != null) {
+ ready &= displayPowerController.requestPowerState(request,
+ waitForNegativeProximity);
+ }
}
}
diff --git a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
index 4e341a9c19b4..a43f93a9beac 100644
--- a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
+++ b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
@@ -16,6 +16,8 @@
package com.android.server.display;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_MAX;
+
import android.annotation.Nullable;
import android.hardware.display.DisplayManagerInternal;
import android.os.PowerManager;
@@ -65,6 +67,22 @@ public class DisplayOffloadSessionImpl implements DisplayManagerInternal.Display
return true;
}
+ @Override
+ public float[] getAutoBrightnessLevels(int mode) {
+ if (mode < 0 || mode > AUTO_BRIGHTNESS_MODE_MAX) {
+ throw new IllegalArgumentException("Unknown auto-brightness mode: " + mode);
+ }
+ return mDisplayPowerController.getAutoBrightnessLevels(mode);
+ }
+
+ @Override
+ public float[] getAutoBrightnessLuxLevels(int mode) {
+ if (mode < 0 || mode > AUTO_BRIGHTNESS_MODE_MAX) {
+ throw new IllegalArgumentException("Unknown auto-brightness mode: " + mode);
+ }
+ return mDisplayPowerController.getAutoBrightnessLuxLevels(mode);
+ }
+
/**
* Start the offload session. The method returns if the session is already active.
* @return Whether the session was started successfully
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 06e5f99a1946..734381b1ddb0 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -2214,6 +2214,20 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
@Override
+ public float[] getAutoBrightnessLevels(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
+ // The old DPC is no longer supported
+ return null;
+ }
+
+ @Override
+ public float[] getAutoBrightnessLuxLevels(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
+ // The old DPC is no longer supported
+ return null;
+ }
+
+ @Override
public BrightnessInfo getBrightnessInfo() {
synchronized (mCachedBrightnessInfo) {
return new BrightnessInfo(
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 519224a5fcff..7df61142475a 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -1886,6 +1886,24 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
}
@Override
+ public float[] getAutoBrightnessLevels(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
+ int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL, UserHandle.USER_CURRENT);
+ return mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(mode, preset);
+ }
+
+ @Override
+ public float[] getAutoBrightnessLuxLevels(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
+ int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
+ Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL, UserHandle.USER_CURRENT);
+ return mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(mode, preset);
+ }
+
+ @Override
public BrightnessInfo getBrightnessInfo() {
synchronized (mCachedBrightnessInfo) {
return new BrightnessInfo(
diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
index c27918430254..13acb3f79c67 100644
--- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
+++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
@@ -237,4 +237,21 @@ public interface DisplayPowerControllerInterface {
* Indicate that boot has been completed and the screen is ready to update.
*/
void onBootCompleted();
+
+ /**
+ * Get the brightness levels used to determine automatic brightness based on lux levels.
+ * @param mode The auto-brightness mode
+ * @return The brightness levels for the specified mode. The values are between
+ * {@link PowerManager.BRIGHTNESS_MIN} and {@link PowerManager.BRIGHTNESS_MAX}.
+ */
+ float[] getAutoBrightnessLevels(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode);
+
+ /**
+ * Get the lux levels used to determine automatic brightness.
+ * @param mode The auto-brightness mode
+ * @return The lux levels for the specified mode
+ */
+ float[] getAutoBrightnessLuxLevels(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode);
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index f994c0556c82..bcf27b4e8f0a 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -26,9 +26,12 @@ import android.util.Slog;
import android.view.Choreographer;
import android.view.Display;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BackgroundThread;
import com.android.server.display.utils.DebugUtils;
import java.io.PrintWriter;
+import java.util.concurrent.Executor;
/**
* Controls the display power state.
@@ -75,10 +78,19 @@ final class DisplayPowerState {
private Runnable mCleanListener;
+ private Executor mAsyncDestroyExecutor;
+
private volatile boolean mStopped;
DisplayPowerState(
DisplayBlanker blanker, ColorFade colorFade, int displayId, int displayState) {
+ this(blanker, colorFade, displayId, displayState, BackgroundThread.getExecutor());
+ }
+
+ @VisibleForTesting
+ DisplayPowerState(
+ DisplayBlanker blanker, ColorFade colorFade, int displayId, int displayState,
+ Executor asyncDestroyExecutor) {
mHandler = new Handler(true /*async*/);
mChoreographer = Choreographer.getInstance();
mBlanker = blanker;
@@ -86,6 +98,7 @@ final class DisplayPowerState {
mPhotonicModulator = new PhotonicModulator();
mPhotonicModulator.start();
mDisplayId = displayId;
+ mAsyncDestroyExecutor = asyncDestroyExecutor;
// At boot time, we don't know the screen's brightness,
// so prepare to set it to a known state when the state is next applied.
@@ -321,7 +334,7 @@ final class DisplayPowerState {
mStopped = true;
mPhotonicModulator.interrupt();
if (mColorFade != null) {
- mColorFade.destroy();
+ mAsyncDestroyExecutor.execute(mColorFade::destroy);
}
mCleanListener = null;
mHandler.removeCallbacksAndMessages(null);
diff --git a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
index 200d88a78dd7..01a8d360a526 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
@@ -104,8 +104,7 @@ public class HdrClamper {
public void resetHdrConfig(HdrBrightnessData data, int width, int height,
float minimumHdrPercentOfScreen, IBinder displayToken) {
mHdrBrightnessData = data;
- mHdrListener.mHdrMinPixels = minimumHdrPercentOfScreen <= 0 ? -1
- : (float) (width * height) * minimumHdrPercentOfScreen;
+ mHdrListener.mHdrMinPixels = (float) (width * height) * minimumHdrPercentOfScreen;
if (displayToken != mRegisteredDisplayToken) { // token changed, resubscribe
if (mRegisteredDisplayToken != null) { // previous token not null, unsubscribe
mHdrListener.unregister(mRegisteredDisplayToken);
@@ -115,7 +114,7 @@ public class HdrClamper {
// new token not null and hdr min % of the screen is set, subscribe.
// e.g. for virtual display, HBM data will be missing and HdrListener
// should not be registered
- if (displayToken != null && mHdrListener.mHdrMinPixels > 0) {
+ if (displayToken != null && mHdrListener.mHdrMinPixels >= 0) {
mHdrListener.register(displayToken);
mRegisteredDisplayToken = displayToken;
}
@@ -140,8 +139,11 @@ public class HdrClamper {
pw.println(" mDesiredMaxBrightness=" + mDesiredMaxBrightness);
pw.println(" mTransitionRate=" + mTransitionRate);
pw.println(" mDesiredTransitionRate=" + mDesiredTransitionRate);
+ pw.println(" mHdrVisible=" + mHdrVisible);
+ pw.println(" mHdrListener.mHdrMinPixels=" + mHdrListener.mHdrMinPixels);
pw.println(" mHdrBrightnessData=" + (mHdrBrightnessData == null ? "null"
: mHdrBrightnessData.toString()));
+ pw.println(" mHdrListener registered=" + (mRegisteredDisplayToken != null));
pw.println(" mAmbientLux=" + mAmbientLux);
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 24e23003d12b..087c52573118 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -2523,9 +2523,9 @@ public class InputManagerService extends IInputManager.Stub
// Native callback.
@SuppressWarnings("unused")
private int interceptMotionBeforeQueueingNonInteractive(int displayId,
- long whenNanos, int policyFlags) {
+ int source, int action, long whenNanos, int policyFlags) {
return mWindowManagerCallbacks.interceptMotionBeforeQueueingNonInteractive(
- displayId, whenNanos, policyFlags);
+ displayId, source, action, whenNanos, policyFlags);
}
// Native callback.
@@ -2901,8 +2901,8 @@ public class InputManagerService extends IInputManager.Stub
* processing when the device is in a non-interactive state since these events are normally
* dropped.
*/
- int interceptMotionBeforeQueueingNonInteractive(int displayId, long whenNanos,
- int policyFlags);
+ int interceptMotionBeforeQueueingNonInteractive(int displayId, int source, int action,
+ long whenNanos, int policyFlags);
/**
* This callback is invoked just before the key is about to be sent to an application.
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index a6c5ad5ffc6c..a88d85e07100 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -229,31 +229,6 @@ final class InputMethodUtils {
}
}
- private static List<Pair<String, ArrayList<String>>> buildInputMethodsAndSubtypeList(
- String enabledInputMethodsStr,
- TextUtils.SimpleStringSplitter inputMethodSplitter,
- TextUtils.SimpleStringSplitter subtypeSplitter) {
- ArrayList<Pair<String, ArrayList<String>>> imsList = new ArrayList<>();
- if (TextUtils.isEmpty(enabledInputMethodsStr)) {
- return imsList;
- }
- inputMethodSplitter.setString(enabledInputMethodsStr);
- while (inputMethodSplitter.hasNext()) {
- String nextImsStr = inputMethodSplitter.next();
- subtypeSplitter.setString(nextImsStr);
- if (subtypeSplitter.hasNext()) {
- ArrayList<String> subtypeHashes = new ArrayList<>();
- // The first element is ime id.
- String imeId = subtypeSplitter.next();
- while (subtypeSplitter.hasNext()) {
- subtypeHashes.add(subtypeSplitter.next());
- }
- imsList.add(new Pair<>(imeId, subtypeHashes));
- }
- }
- return imsList;
- }
-
InputMethodSettings(ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId,
boolean copyOnWrite) {
mMethodMap = methodMap;
@@ -351,9 +326,30 @@ final class InputMethodUtils {
}
List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
- return buildInputMethodsAndSubtypeList(getEnabledInputMethodsStr(),
- new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR),
- new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR));
+ final String enabledInputMethodsStr = getEnabledInputMethodsStr();
+ final TextUtils.SimpleStringSplitter inputMethodSplitter =
+ new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
+ final TextUtils.SimpleStringSplitter subtypeSplitter =
+ new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
+ final ArrayList<Pair<String, ArrayList<String>>> imsList = new ArrayList<>();
+ if (TextUtils.isEmpty(enabledInputMethodsStr)) {
+ return imsList;
+ }
+ inputMethodSplitter.setString(enabledInputMethodsStr);
+ while (inputMethodSplitter.hasNext()) {
+ String nextImsStr = inputMethodSplitter.next();
+ subtypeSplitter.setString(nextImsStr);
+ if (subtypeSplitter.hasNext()) {
+ ArrayList<String> subtypeHashes = new ArrayList<>();
+ // The first element is ime id.
+ String imeId = subtypeSplitter.next();
+ while (subtypeSplitter.hasNext()) {
+ subtypeHashes.add(subtypeSplitter.next());
+ }
+ imsList.add(new Pair<>(imeId, subtypeHashes));
+ }
+ }
+ return imsList;
}
void appendAndPutEnabledInputMethodLocked(String id) {
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index 27b01a5424b8..9c4225dc2542 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -67,6 +67,7 @@ import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationRequest;
import android.location.LocationResult;
+import android.location.LocationResult.BadLocationException;
import android.location.flags.Flags;
import android.location.provider.ProviderProperties;
import android.location.provider.ProviderRequest;
@@ -1380,7 +1381,11 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
location.setExtras(mLocationExtras.getBundle());
- reportLocation(LocationResult.wrap(location).validate());
+ try {
+ reportLocation(LocationResult.wrap(location).validate());
+ } catch (BadLocationException e) {
+ throw new IllegalArgumentException(e);
+ }
if (mStarted) {
mGnssMetrics.logReceivedLocationStatus(hasLatLong);
@@ -1751,7 +1756,11 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
}
}
- reportLocation(LocationResult.wrap(locations).validate());
+ try {
+ reportLocation(LocationResult.wrap(locations).validate());
+ } catch (BadLocationException e) {
+ throw new IllegalArgumentException(e);
+ }
}
Runnable[] listeners;
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 91e6a80a5bbb..7d44aecf5607 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -64,6 +64,7 @@ import android.location.LocationManagerInternal;
import android.location.LocationManagerInternal.ProviderEnabledListener;
import android.location.LocationRequest;
import android.location.LocationResult;
+import android.location.LocationResult.BadLocationException;
import android.location.altitude.AltitudeConverter;
import android.location.provider.IProviderRequestListener;
import android.location.provider.ProviderProperties;
@@ -910,7 +911,8 @@ public class LocationProviderManager extends
< getRequest().getMinUpdateIntervalMillis() - maxJitterMs) {
if (D) {
Log.v(TAG, mName + " provider registration " + getIdentity()
- + " dropped delivery - too fast");
+ + " dropped delivery - too fast (deltaMs="
+ + deltaMs + ").");
}
return false;
}
@@ -2574,29 +2576,17 @@ public class LocationProviderManager extends
@GuardedBy("mMultiplexerLock")
@Nullable
private LocationResult processReportedLocation(LocationResult locationResult) {
- LocationResult processed = locationResult.filter(location -> {
- if (!location.isMock()) {
- if (location.getLatitude() == 0 && location.getLongitude() == 0) {
- Log.e(TAG, "blocking 0,0 location from " + mName + " provider");
- return false;
- }
- }
-
- if (!location.isComplete()) {
- Log.e(TAG, "blocking incomplete location from " + mName + " provider");
- return false;
- }
-
- return true;
- });
- if (processed == null) {
+ try {
+ locationResult.validate();
+ } catch (BadLocationException e) {
+ Log.e(TAG, "Dropping invalid locations: " + e);
return null;
}
// Attempt to add a missing MSL altitude on behalf of the provider.
if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_LOCATION,
"enable_location_provider_manager_msl", true)) {
- return processed.map(location -> {
+ return locationResult.map(location -> {
if (!location.hasMslAltitude() && location.hasAltitude()) {
try {
Location locationCopy = new Location(location);
@@ -2626,7 +2616,7 @@ public class LocationProviderManager extends
return location;
});
}
- return processed;
+ return locationResult;
}
@GuardedBy("mMultiplexerLock")
diff --git a/services/core/java/com/android/server/location/provider/MockLocationProvider.java b/services/core/java/com/android/server/location/provider/MockLocationProvider.java
index 52b04d4bab14..4efacd7a2995 100644
--- a/services/core/java/com/android/server/location/provider/MockLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/MockLocationProvider.java
@@ -21,6 +21,7 @@ import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import android.annotation.Nullable;
import android.location.Location;
import android.location.LocationResult;
+import android.location.LocationResult.BadLocationException;
import android.location.provider.ProviderProperties;
import android.location.provider.ProviderRequest;
import android.location.util.identity.CallerIdentity;
@@ -55,7 +56,11 @@ public class MockLocationProvider extends AbstractLocationProvider {
Location location = new Location(l);
location.setIsFromMockProvider(true);
mLocation = location;
- reportLocation(LocationResult.wrap(location).validate());
+ try {
+ reportLocation(LocationResult.wrap(location).validate());
+ } catch (BadLocationException e) {
+ throw new IllegalArgumentException(e);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
index 05966da28217..a597edd93515 100644
--- a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
@@ -305,7 +305,7 @@ public class ProxyLocationProvider extends AbstractLocationProvider implements
return;
}
- reportLocation(LocationResult.wrap(location).validate());
+ reportLocation(LocationResult.wrap(location));
}
}
@@ -316,8 +316,7 @@ public class ProxyLocationProvider extends AbstractLocationProvider implements
if (mProxy != this) {
return;
}
-
- reportLocation(LocationResult.wrap(locations).validate());
+ reportLocation(LocationResult.wrap(locations));
}
}
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 38f0df41db04..9088cb96a29d 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -2700,11 +2700,8 @@ class MediaRouter2ServiceImpl {
// session info from them.
sessionInfo = mSystemProvider.getDefaultSessionInfo();
}
- // TODO: b/279555229 - replace with matchingRequest.mRouterRecord.notifySessionCreated.
- notifySessionCreatedToRouter(
- matchingRequest.mRouterRecord,
- toOriginalRequestId(uniqueRequestId),
- sessionInfo);
+ matchingRequest.mRouterRecord.notifySessionCreated(
+ toOriginalRequestId(uniqueRequestId), sessionInfo);
}
private void onSessionInfoChangedOnHandler(@NonNull MediaRoute2Provider provider,
@@ -2812,11 +2809,6 @@ class MediaRouter2ServiceImpl {
return true;
}
- private void notifySessionCreatedToRouter(@NonNull RouterRecord routerRecord,
- int requestId, @NonNull RoutingSessionInfo sessionInfo) {
- routerRecord.notifySessionCreated(requestId, sessionInfo);
- }
-
private void notifySessionCreationFailedToRouter(@NonNull RouterRecord routerRecord,
int requestId) {
try {
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index b424c2083bd4..07b333a0fda6 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -16,6 +16,7 @@
package com.android.server.media;
+import android.app.ForegroundServiceDelegationOptions;
import android.media.MediaController2;
import android.media.Session2CommandGroup;
import android.media.Session2Token;
@@ -89,6 +90,12 @@ public class MediaSession2Record implements MediaSessionRecordImpl {
}
@Override
+ public ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions() {
+ // TODO: Implement when MediaSession2 knows about its owner pid.
+ return null;
+ }
+
+ @Override
public boolean isSystemPriority() {
// System priority session is currently only allowed for telephony, so it's OK to stick to
// the media1 API at this moment.
@@ -217,7 +224,8 @@ public class MediaSession2Record implements MediaSessionRecordImpl {
synchronized (mLock) {
service = mService;
}
- service.onSessionPlaybackStateChanged(MediaSession2Record.this, playbackActive);
+ service.onSessionPlaybackStateChanged(
+ MediaSession2Record.this, playbackActive, /* playbackState= */ null);
}
}
}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 994d3ca1124f..cce66e28a8f9 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -29,6 +29,7 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
+import android.app.ForegroundServiceDelegationOptions;
import android.app.PendingIntent;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
@@ -182,6 +183,8 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
private final Context mContext;
private final boolean mVolumeAdjustmentForRemoteGroupSessions;
+ private final ForegroundServiceDelegationOptions mForegroundServiceDelegationOptions;
+
private final Object mLock = new Object();
private final CopyOnWriteArrayList<ISessionControllerCallbackHolder>
mControllerCallbackHolders = new CopyOnWriteArrayList<>();
@@ -244,10 +247,32 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
mVolumeAdjustmentForRemoteGroupSessions = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions);
+ mForegroundServiceDelegationOptions = createForegroundServiceDelegationOptions();
+
// May throw RemoteException if the session app is killed.
mSessionCb.mCb.asBinder().linkToDeath(this, 0);
}
+ private ForegroundServiceDelegationOptions createForegroundServiceDelegationOptions() {
+ return new ForegroundServiceDelegationOptions.Builder()
+ .setClientPid(mOwnerPid)
+ .setClientUid(getUid())
+ .setClientPackageName(getPackageName())
+ .setClientAppThread(null)
+ .setSticky(false)
+ .setClientInstanceName(
+ "MediaSessionFgsDelegate_"
+ + getUid()
+ + "_"
+ + mOwnerPid
+ + "_"
+ + getPackageName())
+ .setForegroundServiceTypes(0)
+ .setDelegationService(
+ ForegroundServiceDelegationOptions.DELEGATION_SERVICE_MEDIA_PLAYBACK)
+ .build();
+ }
+
/**
* Get the session binder for the {@link MediaSession}.
*
@@ -681,6 +706,11 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
return mPackageName + "/" + mTag + " (userId=" + mUserId + ")";
}
+ @Override
+ public ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions() {
+ return mForegroundServiceDelegationOptions;
+ }
+
private void postAdjustLocalVolume(final int stream, final int direction, final int flags,
final String callingOpPackageName, final int callingPid, final int callingUid,
final boolean asSystemService, final boolean useSuggested,
@@ -1273,7 +1303,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
final long token = Binder.clearCallingIdentity();
try {
mService.onSessionPlaybackStateChanged(
- MediaSessionRecord.this, shouldUpdatePriority);
+ MediaSessionRecord.this, shouldUpdatePriority, mPlaybackState);
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
index 8f01f02f2ab1..99c8ea93936e 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
@@ -16,7 +16,9 @@
package com.android.server.media;
+import android.app.ForegroundServiceDelegationOptions;
import android.media.AudioManager;
+import android.media.session.PlaybackState;
import android.os.ResultReceiver;
import android.view.KeyEvent;
@@ -51,6 +53,15 @@ public interface MediaSessionRecordImpl extends AutoCloseable {
int getUserId();
/**
+ * Get the {@link ForegroundServiceDelegationOptions} needed for notifying activity manager
+ * service with changes in the {@link PlaybackState} for this session.
+ *
+ * @return the {@link ForegroundServiceDelegationOptions} needed for notifying the activity
+ * manager service with changes in the {@link PlaybackState} for this session.
+ */
+ ForegroundServiceDelegationOptions getForegroundServiceDelegationOptions();
+
+ /**
* Check if this session has system priority and should receive media buttons before any other
* sessions.
*
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 2c595116afed..db39b5e24bbb 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -29,6 +29,8 @@ import static com.android.server.media.MediaKeyDispatcher.isTripleTapOverridden;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.ForegroundServiceDelegationOptions;
import android.app.KeyguardManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -59,6 +61,7 @@ import android.media.session.ISessionManager;
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.MediaSessionManager;
+import android.media.session.PlaybackState;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -144,6 +147,7 @@ public class MediaSessionService extends SystemService implements Monitor {
private AudioManager mAudioManager;
private boolean mHasFeatureLeanback;
private ActivityManagerLocal mActivityManagerLocal;
+ private ActivityManagerInternal mActivityManagerInternal;
// The FullUserRecord of the current users. (i.e. The foreground user that isn't a profile)
// It's always not null after the MediaSessionService is started.
@@ -229,6 +233,7 @@ public class MediaSessionService extends SystemService implements Monitor {
mContext.registerReceiver(mNotificationListenerEnabledChangedReceiver, filter);
mActivityManagerLocal = LocalManagerRegistry.getManager(ActivityManagerLocal.class);
+ mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
}
@Override
@@ -285,7 +290,8 @@ public class MediaSessionService extends SystemService implements Monitor {
}
user.mPriorityStack.onSessionActiveStateChanged(record);
}
-
+ setForegroundServiceAllowance(
+ record, /* allowRunningInForeground= */ record.isActive());
mHandler.postSessionsChanged(record);
}
}
@@ -371,8 +377,10 @@ public class MediaSessionService extends SystemService implements Monitor {
}
}
- void onSessionPlaybackStateChanged(MediaSessionRecordImpl record,
- boolean shouldUpdatePriority) {
+ void onSessionPlaybackStateChanged(
+ MediaSessionRecordImpl record,
+ boolean shouldUpdatePriority,
+ @Nullable PlaybackState playbackState) {
synchronized (mLock) {
FullUserRecord user = getFullUserRecordLocked(record.getUserId());
if (user == null || !user.mPriorityStack.contains(record)) {
@@ -380,6 +388,10 @@ public class MediaSessionService extends SystemService implements Monitor {
return;
}
user.mPriorityStack.onPlaybackStateChanged(record, shouldUpdatePriority);
+ if (playbackState != null) {
+ setForegroundServiceAllowance(
+ record, playbackState.shouldAllowServiceToRunInForeground());
+ }
}
}
@@ -543,9 +555,30 @@ public class MediaSessionService extends SystemService implements Monitor {
}
session.close();
+ setForegroundServiceAllowance(session, /* allowRunningInForeground= */ false);
mHandler.postSessionsChanged(session);
}
+ private void setForegroundServiceAllowance(
+ MediaSessionRecordImpl record, boolean allowRunningInForeground) {
+ if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
+ return;
+ }
+ ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
+ record.getForegroundServiceDelegationOptions();
+ if (foregroundServiceDelegationOptions == null) {
+ // This record doesn't support FGS delegation. In practice, this is MediaSession2.
+ return;
+ }
+ if (allowRunningInForeground) {
+ mActivityManagerInternal.startForegroundServiceDelegate(
+ foregroundServiceDelegationOptions, /* connection= */ null);
+ } else {
+ mActivityManagerInternal.stopForegroundServiceDelegate(
+ foregroundServiceDelegationOptions);
+ }
+ }
+
void tempAllowlistTargetPkgIfPossible(int targetUid, String targetPackage,
int callingPid, int callingUid, String callingPackage, String reason) {
final long token = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 46e7041b8898..e4e48bdd35d1 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -4090,6 +4090,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
fout.decreaseIndent();
+ fout.println();
fout.println("Admin restricted uids for metered data:");
fout.increaseIndent();
size = mMeteredRestrictedUids.size();
@@ -4099,6 +4100,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
fout.decreaseIndent();
+ fout.println();
fout.println("Network to interfaces:");
fout.increaseIndent();
for (int i = 0; i < mNetworkToIfaces.size(); ++i) {
@@ -4108,6 +4110,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
fout.decreaseIndent();
fout.println();
+ fout.print("Active notifications: ");
+ fout.println(mActiveNotifs);
+
+ fout.println();
mStatLogger.dump(fout);
mLogger.dumpLogs(fout);
@@ -6672,7 +6678,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
* Build unique tag that identifies an active {@link NetworkPolicy}
* notification of a specific type, like {@link #TYPE_LIMIT}.
*/
- private String buildNotificationTag(NetworkPolicy policy, int type) {
+ private static String buildNotificationTag(NetworkPolicy policy, int type) {
return TAG + ":" + policy.template.hashCode() + ":" + type;
}
@@ -6683,5 +6689,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
public int getId() {
return mId;
}
+
+ @Override
+ public String toString() {
+ return mTag;
+ }
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index f49d51c70c1d..ff415c155b35 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -5472,20 +5472,13 @@ public class NotificationManagerService extends SystemService {
}
@Override
- public void setAutomaticZenRuleState(String id, Condition condition, boolean fromUser) {
+ public void setAutomaticZenRuleState(String id, Condition condition) {
Objects.requireNonNull(id, "id is null");
Objects.requireNonNull(condition, "Condition is null");
condition.validate();
enforcePolicyAccess(Binder.getCallingUid(), "setAutomaticZenRuleState");
-
- if (android.app.Flags.modesApi()) {
- if (fromUser != (condition.source == Condition.SOURCE_USER_ACTION)) {
- throw new IllegalArgumentException(String.format(
- "Mismatch between fromUser (%s) and condition.source (%s)",
- fromUser, Condition.sourceToString(condition.source)));
- }
- }
+ boolean fromUser = (condition.source == Condition.SOURCE_USER_ACTION);
mZenModeHelper.setAutomaticZenRuleState(id, condition, computeZenOrigin(fromUser),
Binder.getCallingUid());
@@ -5508,7 +5501,7 @@ public class NotificationManagerService extends SystemService {
if (android.app.Flags.modesApi()
&& fromUser
&& !isCallerSystemOrSystemUiOrShell()) {
- throw new SecurityException(String.format(
+ throw new SecurityException(TextUtils.formatSimple(
"Calling %s with fromUser == true is only allowed for system", method));
}
}
@@ -10754,6 +10747,14 @@ public class NotificationManagerService extends SystemService {
final String key = record.getSbn().getKey();
final NotificationListenerService.Ranking ranking =
new NotificationListenerService.Ranking();
+ ArrayList<Notification.Action> smartActions = record.getSystemGeneratedSmartActions();
+ ArrayList<CharSequence> smartReplies = record.getSmartReplies();
+ if (redactSensitiveNotificationsFromUntrustedListeners()
+ && !mListeners.isUidTrusted(info.uid)
+ && mListeners.hasSensitiveContent(record)) {
+ smartActions = null;
+ smartReplies = null;
+ }
ranking.populate(
key,
rankings.size(),
@@ -10771,8 +10772,8 @@ public class NotificationManagerService extends SystemService {
record.isHidden(),
record.getLastAudiblyAlertedMs(),
record.getSound() != null || record.getVibration() != null,
- record.getSystemGeneratedSmartActions(),
- record.getSmartReplies(),
+ smartActions,
+ smartReplies,
record.canBubble(),
record.isTextChanged(),
record.isConversation(),
@@ -11522,20 +11523,16 @@ public class NotificationManagerService extends SystemService {
super.setPackageOrComponentEnabled(pkgOrComponent, userId, isPrimary, enabled, userSet);
String pkgName = getPackageName(pkgOrComponent);
if (redactSensitiveNotificationsFromUntrustedListeners()) {
- try {
- int uid = mPackageManagerClient.getPackageUidAsUser(pkgName, userId);
- if (!enabled) {
- synchronized (mTrustedListenerUids) {
- mTrustedListenerUids.remove(uid);
- }
+ int uid = mPackageManagerInternal.getPackageUid(pkgName, 0, userId);
+ if (!enabled && uid >= 0) {
+ synchronized (mTrustedListenerUids) {
+ mTrustedListenerUids.remove(uid);
}
- if (enabled && isAppTrustedNotificationListenerService(uid, pkgName)) {
- synchronized (mTrustedListenerUids) {
- mTrustedListenerUids.add(uid);
- }
+ }
+ if (enabled && uid >= 0 && isAppTrustedNotificationListenerService(uid, pkgName)) {
+ synchronized (mTrustedListenerUids) {
+ mTrustedListenerUids.add(uid);
}
- } catch (NameNotFoundException e) {
- Slog.e(TAG, "PackageManager could not find package " + pkgName, e);
}
}
@@ -11955,8 +11952,10 @@ public class NotificationManagerService extends SystemService {
for (final ManagedServiceInfo info : getServices()) {
boolean isTrusted = isUidTrusted(info.uid);
- boolean sendRedacted = isNewSensitive && !isTrusted;
- boolean sendOldRedacted = isOldSensitive && !isTrusted;
+ boolean sendRedacted = redactSensitiveNotificationsFromUntrustedListeners()
+ && isNewSensitive && !isTrusted;
+ boolean sendOldRedacted = redactSensitiveNotificationsFromUntrustedListeners()
+ && isOldSensitive && !isTrusted;
boolean sbnVisible = isVisibleToListener(sbn, r.getNotificationType(), info);
boolean oldSbnVisible = (oldSbn != null)
&& isVisibleToListener(oldSbn, old.getNotificationType(), info);
@@ -12055,7 +12054,7 @@ public class NotificationManagerService extends SystemService {
StatusBarNotification redactStatusBarNotification(StatusBarNotification sbn) {
if (!redactSensitiveNotificationsFromUntrustedListeners()) {
- return sbn;
+ throw new RuntimeException("redactStatusBarNotification called while flag is off");
}
ApplicationInfo appInfo = sbn.getNotification().extras.getParcelable(
@@ -12227,6 +12226,7 @@ public class NotificationManagerService extends SystemService {
public void notifyRankingUpdateLocked(List<NotificationRecord> changedHiddenNotifications) {
boolean isHiddenRankingUpdate = changedHiddenNotifications != null
&& changedHiddenNotifications.size() > 0;
+
// TODO (b/73052211): if the ranking update changed the notification type,
// cancel notifications for NLSes that can't see them anymore
for (final ManagedServiceInfo serviceInfo : getServices()) {
@@ -12250,7 +12250,6 @@ public class NotificationManagerService extends SystemService {
if (notifyThisListener || !isHiddenRankingUpdate) {
final NotificationRankingUpdate update = makeRankingUpdateLocked(
serviceInfo);
-
mHandler.post(() -> notifyRankingUpdate(serviceInfo, update));
}
}
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index 7f58e75e0287..e830d288e204 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -827,7 +827,8 @@ public final class BroadcastHelper {
// action. When the targetPkg is set, it sends the broadcast to specific app, e.g.
// installer app or null for registered apps. The callback only need to send back to the
// registered apps so we check the null condition here.
- notifyPackageMonitor(action, pkg, extras, userIds, instantUserIds, broadcastAllowList);
+ notifyPackageMonitor(action, pkg, extras, userIds, instantUserIds, broadcastAllowList,
+ null /* filterExtras */);
}
}
@@ -975,14 +976,16 @@ public final class BroadcastHelper {
final Bundle options = new BroadcastOptions()
.setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
.toBundle();
+ BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver =
+ (callingUid, intentExtras) -> BroadcastHelper.filterExtrasChangedPackageList(
+ snapshot, callingUid, intentExtras);
mHandler.post(() -> sendPackageBroadcast(intent, null /* pkg */,
extras, flags, null /* targetPkg */, null /* finishedReceiver */,
new int[]{userId}, null /* instantUserIds */, null /* broadcastAllowList */,
- (callingUid, intentExtras) -> BroadcastHelper.filterExtrasChangedPackageList(
- snapshot, callingUid, intentExtras),
+ filterExtrasForReceiver,
options));
notifyPackageMonitor(intent, null /* pkg */, extras, new int[]{userId},
- null /* instantUserIds */, null /* broadcastAllowList */);
+ null /* instantUserIds */, null /* broadcastAllowList */, filterExtrasForReceiver);
}
void sendMyPackageSuspendedOrUnsuspended(@NonNull Computer snapshot,
@@ -1068,9 +1071,10 @@ public final class BroadcastHelper {
@Nullable Bundle extras,
@NonNull int[] userIds,
@NonNull int[] instantUserIds,
- @Nullable SparseArray<int[]> broadcastAllowList) {
+ @Nullable SparseArray<int[]> broadcastAllowList,
+ @Nullable BiFunction<Integer, Bundle, Bundle> filterExtras) {
mPackageMonitorCallbackHelper.notifyPackageMonitor(action, pkg, extras, userIds,
- instantUserIds, broadcastAllowList, mHandler);
+ instantUserIds, broadcastAllowList, mHandler, filterExtras);
}
private void notifyResourcesChanged(boolean mediaStatus,
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index b96b70419b74..c920ca89b75e 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -829,6 +829,9 @@ final class DeletePackageHelper {
int returnCodeOfChild;
for (int childId : childUserIds) {
if (childId == userId) continue;
+ if (mUserManagerInternal.getProfileParentId(childId) != userId) {
+ continue;
+ }
// If package is not present in child then don't attempt to delete.
if (!packageState.getUserStateOrDefault(childId).isInstalled()) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 81d5d8195960..5225529ef001 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -607,6 +607,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService
private final boolean mIsUpgrade;
private final boolean mIsPreNMR1Upgrade;
private final boolean mIsPreQUpgrade;
+ // If mIsUpgrade == true, contains the prior SDK version, else -1.
+ private final int mPriorSdkVersion;
// Used for privilege escalation. MUST NOT BE CALLED WITH mPackages
// LOCK HELD. Can be called with mInstallLock held.
@@ -1891,6 +1893,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
mInstantAppResolverSettingsComponent = testParams.instantAppResolverSettingsComponent;
mIsPreNMR1Upgrade = testParams.isPreNmr1Upgrade;
mIsPreQUpgrade = testParams.isPreQupgrade;
+ mPriorSdkVersion = testParams.priorSdkVersion;
mIsUpgrade = testParams.isUpgrade;
mMetrics = testParams.Metrics;
mModuleInfoProvider = testParams.moduleInfoProvider;
@@ -2230,7 +2233,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
"Upgrading from " + ver.fingerprint + " (" + ver.buildFingerprint + ") to "
+ PackagePartitions.FINGERPRINT + " (" + Build.FINGERPRINT + ")");
}
-
+ mPriorSdkVersion = mIsUpgrade ? ver.sdkVersion : -1;
mInitAppsHelper = new InitAppsHelper(this, mApexManager, mInstallPackageHelper,
mInjector.getSystemPartitions());
@@ -4623,7 +4626,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
});
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_UNSTOPPED,
packageName, extras, userIds, null /* instantUserIds */,
- broadcastAllowList, mHandler);
+ broadcastAllowList, mHandler, null /* filterExtras */);
}
}
}
@@ -7073,7 +7076,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
}
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_RESTARTED,
packageName, extras, userIds, null /* instantUserIds */,
- broadcastAllowList, mHandler);
+ broadcastAllowList, mHandler, null /* filterExtras */);
}
@Override
@@ -7099,6 +7102,12 @@ public class PackageManagerService implements PackageSender, TestUtilityService
mPackageMonitorCallbackHelper.notifyPackageMonitorWithIntent(intent, userId,
visibilityAllowList, mHandler);
}
+
+ @Override
+ public boolean isUpgradingFromLowerThan(int sdkVersion) {
+ final boolean isUpgrading = mPriorSdkVersion != -1;
+ return isUpgrading && mPriorSdkVersion < sdkVersion;
+ }
}
private void setEnabledOverlayPackages(@UserIdInt int userId,
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index 86d78dce96c3..2d797187b7f1 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -65,6 +65,7 @@ public final class PackageManagerServiceTestParams {
public ComponentName instantAppResolverSettingsComponent;
public boolean isPreNmr1Upgrade;
public boolean isPreQupgrade;
+ public int priorSdkVersion = -1;
public boolean isUpgrade;
public LegacyPermissionManagerInternal legacyPermissionManagerInternal;
public DisplayMetrics Metrics;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 243fb16b19ae..5724ee0d94e9 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -1085,8 +1085,14 @@ class PackageManagerShellCommand extends ShellCommand {
// the sdk or package name along with optional additional information based on opt.
final Map<String, List<String>> out = new HashMap<>();
for (int userId : userIds) {
- final int translatedUserId =
+ final int translatedUserId;
+ try {
+ translatedUserId =
translateUserId(userId, UserHandle.USER_SYSTEM, "runListPackages");
+ } catch (RuntimeException ex) {
+ getErrPrintWriter().println("Error: " + ex.toString());
+ continue;
+ }
@SuppressWarnings("unchecked") final ParceledListSlice<PackageInfo> slice =
mInterface.getInstalledPackages(getFlags, translatedUserId);
final List<PackageInfo> packages = slice.getList();
diff --git a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
index 1bb0730cccb2..d05e4c69427e 100644
--- a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
+++ b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
@@ -41,6 +41,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import java.util.ArrayList;
+import java.util.function.BiFunction;
/** Helper class to handle PackageMonitorCallback and notify the registered client. This is mainly
* used by PackageMonitor to improve the broadcast latency. */
@@ -105,8 +106,9 @@ class PackageMonitorCallbackHelper {
extras.putBoolean(Intent.EXTRA_ARCHIVAL, true);
}
extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType);
- notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED, packageName, extras ,
- userIds /* userIds */, instantUserIds, broadcastAllowList, handler);
+ notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED, packageName, extras,
+ userIds /* userIds */, instantUserIds, broadcastAllowList, handler,
+ null /* filterExtras */);
}
public void notifyResourcesChanged(boolean mediaStatus, boolean replacing,
@@ -120,7 +122,8 @@ class PackageMonitorCallbackHelper {
String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE
: Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE;
notifyPackageMonitor(action, null /* pkg */, extras, null /* userIds */,
- null /* instantUserIds */, null /* broadcastAllowList */, handler);
+ null /* instantUserIds */, null /* broadcastAllowList */, handler,
+ null /* filterExtras */);
}
public void notifyPackageChanged(String packageName, boolean dontKillApp,
@@ -137,12 +140,12 @@ class PackageMonitorCallbackHelper {
extras.putString(Intent.EXTRA_REASON, reason);
}
notifyPackageMonitor(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, userIds,
- instantUserIds, broadcastAllowList, handler);
+ instantUserIds, broadcastAllowList, handler, null /* filterExtras */);
}
public void notifyPackageMonitor(String action, String pkg, Bundle extras,
int[] userIds, int[] instantUserIds, SparseArray<int[]> broadcastAllowList,
- Handler handler) {
+ Handler handler, BiFunction<Integer, Bundle, Bundle> filterExtras) {
if (!isAllowedCallbackAction(action)) {
return;
}
@@ -160,10 +163,11 @@ class PackageMonitorCallbackHelper {
if (ArrayUtils.isEmpty(instantUserIds)) {
doNotifyCallbacksByAction(
- action, pkg, extras, resolvedUserIds, broadcastAllowList, handler);
+ action, pkg, extras, resolvedUserIds, broadcastAllowList, handler,
+ filterExtras);
} else {
doNotifyCallbacksByAction(action, pkg, extras, instantUserIds, broadcastAllowList,
- handler);
+ handler, filterExtras);
}
} catch (RemoteException e) {
// do nothing
@@ -199,11 +203,13 @@ class PackageMonitorCallbackHelper {
synchronized (mLock) {
callbacks = mCallbacks;
}
- doNotifyCallbacks(callbacks, intent, userId, broadcastAllowList, handler);
+ doNotifyCallbacks(callbacks, intent, userId, broadcastAllowList, handler,
+ null /* filterExtrasFunction */);
}
private void doNotifyCallbacksByAction(String action, String pkg, Bundle extras, int[] userIds,
- SparseArray<int[]> broadcastAllowList, Handler handler) {
+ SparseArray<int[]> broadcastAllowList, Handler handler,
+ BiFunction<Integer, Bundle, Bundle> filterExtrasFunction) {
RemoteCallbackList<IRemoteCallback> callbacks;
synchronized (mLock) {
callbacks = mCallbacks;
@@ -223,12 +229,13 @@ class PackageMonitorCallbackHelper {
final int[] allowUids =
broadcastAllowList != null ? broadcastAllowList.get(userId) : null;
- doNotifyCallbacks(callbacks, intent, userId, allowUids, handler);
+ doNotifyCallbacks(callbacks, intent, userId, allowUids, handler, filterExtrasFunction);
}
}
private void doNotifyCallbacks(RemoteCallbackList<IRemoteCallback> callbacks,
- Intent intent, int userId, int[] allowUids, Handler handler) {
+ Intent intent, int userId, int[] allowUids, Handler handler,
+ BiFunction<Integer, Bundle, Bundle> filterExtrasFunction) {
handler.post(() -> callbacks.broadcast((callback, user) -> {
RegisterUser registerUser = (RegisterUser) user;
if ((registerUser.getUserId() != UserHandle.USER_ALL) && (registerUser.getUserId()
@@ -236,6 +243,15 @@ class PackageMonitorCallbackHelper {
return;
}
int registerUid = registerUser.getUid();
+ if (filterExtrasFunction != null) {
+ final Bundle extras = intent.getExtras();
+ if (extras != null) {
+ final Bundle filteredExtras = filterExtrasFunction.apply(registerUid, extras);
+ if (filteredExtras != null) {
+ intent.replaceExtras(filteredExtras);
+ }
+ }
+ }
if (allowUids != null && registerUid != Process.SYSTEM_UID
&& !ArrayUtils.contains(allowUids, registerUid)) {
if (DEBUG) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index edae2734c0db..b286b12dcf7d 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -64,6 +64,7 @@ import android.os.Handler;
import android.os.Message;
import android.os.PatternMatcher;
import android.os.PersistableBundle;
+import android.os.Process;
import android.os.SELinux;
import android.os.SystemClock;
import android.os.Trace;
@@ -3189,6 +3190,9 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
pkg.isScannedAsStoppedSystemApp());
if (!pkg.hasSharedUser()) {
serializer.attributeInt(null, "userId", pkg.getAppId());
+
+ serializer.attributeBoolean(null, "isSdkLibrary",
+ pkg.getAndroidPackage() != null && pkg.getAndroidPackage().isSdkLibrary());
} else {
serializer.attributeInt(null, "sharedUserId", pkg.getAppId());
}
@@ -4039,10 +4043,12 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
int targetSdkVersion = 0;
byte[] restrictUpdateHash = null;
boolean isScannedAsStoppedSystemApp = false;
+ boolean isSdkLibrary = false;
try {
name = parser.getAttributeValue(null, ATTR_NAME);
realName = parser.getAttributeValue(null, "realName");
appId = parseAppId(parser);
+ isSdkLibrary = parser.getAttributeBoolean(null, "isSdkLibrary", false);
sharedUserAppId = parseSharedUserAppId(parser);
codePathStr = parser.getAttributeValue(null, "codePath");
@@ -4157,7 +4163,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
PackageManagerService.reportSettingsProblem(Log.WARN,
"Error in package manager settings: <package> has no codePath at "
+ parser.getPositionDescription());
- } else if (appId > 0) {
+ } else if (appId > 0 || (appId == Process.INVALID_UID && isSdkLibrary
+ && Flags.disallowSdkLibsToBeApps())) {
packageSetting = addPackageLPw(name.intern(), realName, new File(codePathStr),
appId, pkgFlags, pkgPrivateFlags, domainSetId);
if (PackageManagerService.DEBUG_SETTINGS)
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index d3931a303d0d..10e6edc2941f 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -26,6 +26,7 @@ import static android.app.AppOpsManager.ATTRIBUTION_FLAGS_NONE;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_ERRORED;
import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.OP_BLUETOOTH_CONNECT;
import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISALLOWED;
import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISCOURAGED;
@@ -1228,6 +1229,11 @@ public class PermissionManagerService extends IPermissionManager.Stub {
sPlatformPermissions.put(permission, permissionInfo);
}
} catch (PackageManager.NameNotFoundException ignored) {
+ // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+ if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) {
+ Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as package"
+ + " not found when retrieving permission info");
+ }
return PermissionChecker.PERMISSION_HARD_DENIED;
}
}
@@ -1347,17 +1353,34 @@ public class PermissionManagerService extends IPermissionManager.Stub {
// way we can avoid the datasource creating an attribution context for every call.
if (!(fromDatasource && current.equals(attributionSource))
&& next != null && !current.isTrusted(context)) {
+ // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+ if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) {
+ Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as "
+ + current + " attribution source isn't a data source and "
+ + current + " isn't trusted");
+ }
return PermissionChecker.PERMISSION_HARD_DENIED;
}
// If we already checked the permission for this one, skip the work
if (!skipCurrentChecks && !checkPermission(context, permissionManagerServiceInt,
permission, current)) {
+ // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+ if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) {
+ Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as we"
+ + " aren't skipping permission checks and permission check returns"
+ + " false for " + current);
+ }
return PermissionChecker.PERMISSION_HARD_DENIED;
}
if (next != null && !checkPermission(context, permissionManagerServiceInt,
permission, next)) {
+ // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+ if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) {
+ Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as"
+ + " permission check returns false for next source " + next);
+ }
return PermissionChecker.PERMISSION_HARD_DENIED;
}
@@ -1402,6 +1425,10 @@ public class PermissionManagerService extends IPermissionManager.Stub {
switch (opMode) {
case AppOpsManager.MODE_ERRORED: {
+ if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) {
+ Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as op"
+ + " mode is MODE_ERRORED for " + attributionSource);
+ }
return PermissionChecker.PERMISSION_HARD_DENIED;
}
case AppOpsManager.MODE_IGNORED: {
@@ -1670,6 +1697,12 @@ public class PermissionManagerService extends IPermissionManager.Stub {
final AttributionSource resolvedAttributionSource = resolveAttributionSource(
context, accessorSource);
if (resolvedAttributionSource.getPackageName() == null) {
+ // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+ if (op == OP_BLUETOOTH_CONNECT) {
+ Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as resolved"
+ + "package name for " + resolvedAttributionSource + " returned"
+ + " null");
+ }
return AppOpsManager.MODE_ERRORED;
}
int notedOp = op;
@@ -1683,6 +1716,13 @@ public class PermissionManagerService extends IPermissionManager.Stub {
if (attributedOp != AppOpsManager.OP_NONE && attributedOp != op) {
checkedOpResult = appOpsManager.checkOpNoThrow(op, resolvedAttributionSource);
if (checkedOpResult == MODE_ERRORED) {
+ // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+ if (op == OP_BLUETOOTH_CONNECT) {
+ Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as"
+ + " checkOp for resolvedAttributionSource "
+ + resolvedAttributionSource + " and op " + op
+ + " returned MODE_ERRORED");
+ }
return checkedOpResult;
}
notedOp = attributedOp;
@@ -1722,7 +1762,22 @@ public class PermissionManagerService extends IPermissionManager.Stub {
throw new SecurityException(msg + ":" + e.getMessage());
}
}
- return Math.max(checkedOpResult, notedOpResult);
+ int result = Math.max(checkedOpResult, notedOpResult);
+ // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+ if (op == OP_BLUETOOTH_CONNECT && result == MODE_ERRORED) {
+ if (result == checkedOpResult) {
+ Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as"
+ + " checkOp for resolvedAttributionSource "
+ + resolvedAttributionSource + " and op " + op
+ + " returned MODE_ERRORED");
+ } else {
+ Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as"
+ + " noteOp for resolvedAttributionSource "
+ + resolvedAttributionSource + " and op " + notedOp
+ + " returned MODE_ERRORED");
+ }
+ }
+ return result;
}
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 3000a1c5c043..5b13d3fead90 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -5201,8 +5201,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// TODO(b/117479243): handle it in InputPolicy
/** {@inheritDoc} */
@Override
- public int interceptMotionBeforeQueueingNonInteractive(int displayId, long whenNanos,
- int policyFlags) {
+ public int interceptMotionBeforeQueueingNonInteractive(int displayId, int source, int action,
+ long whenNanos, int policyFlags) {
if ((policyFlags & FLAG_WAKE) != 0) {
if (wakeUp(whenNanos / 1000000, mAllowTheaterModeWakeFromMotion,
PowerManager.WAKE_REASON_WAKE_MOTION, "android.policy:MOTION")) {
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 03a7bd3b68b3..3016b39a7e6b 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -706,12 +706,14 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
* Generally, it's best to keep as little as possible in the queue thread
* because it's the most fragile.
* @param displayId The display ID of the motion event.
+ * @param source the {@link InputDevice} source that caused the motion.
+ * @param action the {@link MotionEvent} action for the motion.
* @param policyFlags The policy flags associated with the motion.
*
* @return Actions flags: may be {@link #ACTION_PASS_TO_USER}.
*/
- int interceptMotionBeforeQueueingNonInteractive(int displayId, long whenNanos,
- int policyFlags);
+ int interceptMotionBeforeQueueingNonInteractive(int displayId, int source, int action,
+ long whenNanos, int policyFlags);
/**
* Called from the input dispatcher thread before a key is dispatched to a window.
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index 7c833cbe57d4..6f754391f1ca 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -481,6 +481,11 @@ public final class HintManagerService extends SystemService {
protected long mTargetDurationNanos;
protected boolean mUpdateAllowed;
protected int[] mNewThreadIds;
+ protected boolean mPowerEfficient;
+
+ private enum SessionModes {
+ POWER_EFFICIENCY,
+ };
protected AppHintSession(
int uid, int pid, int[] threadIds, IBinder token,
@@ -492,6 +497,7 @@ public final class HintManagerService extends SystemService {
mHalSessionPtr = halSessionPtr;
mTargetDurationNanos = durationNanos;
mUpdateAllowed = true;
+ mPowerEfficient = false;
final boolean allowed = mUidObserver.isUidForeground(mUid);
updateHintAllowed(allowed);
try {
@@ -634,6 +640,9 @@ public final class HintManagerService extends SystemService {
}
Preconditions.checkArgument(mode >= 0, "the mode Id value should be"
+ " greater than zero.");
+ if (mode == SessionModes.POWER_EFFICIENCY.ordinal()) {
+ mPowerEfficient = enabled;
+ }
mNativeWrapper.halSetMode(mHalSessionPtr, mode, enabled);
}
}
@@ -653,6 +662,12 @@ public final class HintManagerService extends SystemService {
}
}
+ public boolean isPowerEfficient() {
+ synchronized (this) {
+ return mPowerEfficient;
+ }
+ }
+
void validateWorkDuration(WorkDuration workDuration) {
if (DEBUG) {
Slogf.d(TAG, "WorkDuration(" + workDuration.getTimestampNanos() + ", "
@@ -718,6 +733,7 @@ public final class HintManagerService extends SystemService {
pw.println(prefix + "SessionTIDs: " + Arrays.toString(mThreadIds));
pw.println(prefix + "SessionTargetDurationNanos: " + mTargetDurationNanos);
pw.println(prefix + "SessionAllowed: " + mUpdateAllowed);
+ pw.println(prefix + "PowerEfficient: " + (mPowerEfficient ? "true" : "false"));
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 145eb3b8464c..ca8afe1972e9 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -223,7 +223,6 @@ import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_F
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
import static com.android.server.wm.ActivityTaskManagerService.getInputDispatchingTimeoutMillisLocked;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.IdentifierProto.HASH_CODE;
import static com.android.server.wm.IdentifierProto.TITLE;
import static com.android.server.wm.IdentifierProto.USER_ID;
@@ -949,7 +948,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
private int mConfigurationSeq;
/**
- * Temp configs used in {@link #ensureActivityConfiguration(int, boolean)}
+ * Temp configs used in {@link #ensureActivityConfiguration()}
*/
private final Configuration mTmpConfig = new Configuration();
private final Rect mTmpBounds = new Rect();
@@ -1511,7 +1510,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
updatePictureInPictureMode(null, false);
} else {
mLastReportedMultiWindowMode = inMultiWindowMode;
- ensureActivityConfiguration(0 /* globalChanges */, PRESERVE_WINDOWS);
+ ensureActivityConfiguration();
}
}
}
@@ -1530,8 +1529,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// precede the configuration change from the resize.
mLastReportedPictureInPictureMode = inPictureInPictureMode;
mLastReportedMultiWindowMode = inPictureInPictureMode;
- ensureActivityConfiguration(0 /* globalChanges */, PRESERVE_WINDOWS,
- true /* ignoreVisibility */);
+ ensureActivityConfiguration(true /* ignoreVisibility */);
if (inPictureInPictureMode && findMainWindow() == null) {
// Prevent malicious app entering PiP without valid WindowState, which can in turn
// result a non-touchable PiP window since the InputConsumer for PiP requires it.
@@ -3107,7 +3105,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// {@link #returningOptions} of the activity under this one can be applied in
// {@link #handleAlreadyVisible()}.
if (changed || !occludesParent) {
- mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+ mRootWindowContainer.ensureActivitiesVisible();
}
return changed;
}
@@ -3747,8 +3745,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
if (ensureVisibility) {
- mDisplayContent.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
- false /* preserveWindows */, true /* notifyClients */);
+ mDisplayContent.ensureActivitiesVisible(null /* starting */,
+ true /* notifyClients */);
}
}
@@ -4165,8 +4163,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (rootTask != null && rootTask.shouldSleepOrShutDownActivities()) {
// Activity is always relaunched to either resumed or paused state. If it was
// relaunched while hidden (by keyguard or smth else), it should be stopped.
- rootTask.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
- false /* preserveWindows */);
+ rootTask.ensureActivitiesVisible(null /* starting */);
}
}
@@ -4681,14 +4678,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
void setShowWhenLocked(boolean showWhenLocked) {
mShowWhenLocked = showWhenLocked;
- mAtmService.mRootWindowContainer.ensureActivitiesVisible(null /* starting */,
- 0 /* configChanges */, false /* preserveWindows */);
+ mAtmService.mRootWindowContainer.ensureActivitiesVisible();
}
void setInheritShowWhenLocked(boolean inheritShowWhenLocked) {
mInheritShownWhenLocked = inheritShowWhenLocked;
- mAtmService.mRootWindowContainer.ensureActivitiesVisible(null /* starting */,
- 0 /* configChanges */, false /* preserveWindows */);
+ mAtmService.mRootWindowContainer.ensureActivitiesVisible();
}
/**
@@ -6413,7 +6408,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
mDisplayContent.handleActivitySizeCompatModeIfNeeded(this);
- mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+ mRootWindowContainer.ensureActivitiesVisible();
}
/**
@@ -7894,7 +7889,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
void applyFixedRotationTransform(DisplayInfo info, DisplayFrames displayFrames,
Configuration config) {
super.applyFixedRotationTransform(info, displayFrames, config);
- ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */);
+ ensureActivityConfiguration();
}
/**
@@ -7989,7 +7984,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
startFreezingScreen(originalDisplayRotation);
// This activity may relaunch or perform configuration change so once it has reported drawn,
// the screen can be unfrozen.
- ensureActivityConfiguration(0 /* globalChanges */, !PRESERVE_WINDOWS);
+ ensureActivityConfiguration();
if (mTransitionController.isCollecting(this)) {
// In case the task was changed from PiP but still keeps old transform.
task.resetSurfaceControlTransforms();
@@ -8017,7 +8012,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// the request is handled at task level with letterbox.
if (!getMergedOverrideConfiguration().equals(
mLastReportedConfiguration.getMergedConfiguration())) {
- ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */,
+ ensureActivityConfiguration(
false /* ignoreVisibility */, true /* isRequestedOrientationChanged */);
if (mTransitionController.inPlayingTransition(this)) {
mTransitionController.mValidateActivityCompat.add(this);
@@ -9525,14 +9520,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return mLastReportedDisplayId != getDisplayId();
}
- boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow) {
- return ensureActivityConfiguration(globalChanges, preserveWindow,
- false /* ignoreVisibility */, false /* isRequestedOrientationChanged */);
+ boolean ensureActivityConfiguration() {
+ return ensureActivityConfiguration(false /* ignoreVisibility */);
}
- boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow,
- boolean ignoreVisibility) {
- return ensureActivityConfiguration(globalChanges, preserveWindow, ignoreVisibility,
+ boolean ensureActivityConfiguration(boolean ignoreVisibility) {
+ return ensureActivityConfiguration(ignoreVisibility,
false /* isRequestedOrientationChanged */);
}
@@ -9540,9 +9533,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
* Make sure the given activity matches the current configuration. Ensures the HistoryRecord
* is updated with the correct configuration and all other bookkeeping is handled.
*
- * @param globalChanges The changes to the global configuration.
- * @param preserveWindow If the activity window should be preserved on screen if the activity
- * is relaunched.
* @param ignoreVisibility If we should try to relaunch the activity even if it is invisible
* (stopped state). This is useful for the case where we know the
* activity will be visible soon and we want to ensure its configuration
@@ -9552,8 +9542,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
* @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 isRequestedOrientationChanged) {
+ boolean ensureActivityConfiguration(boolean ignoreVisibility,
+ boolean isRequestedOrientationChanged) {
final Task rootTask = getRootTask();
if (rootTask.mConfigWillChange) {
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Skipping config check "
@@ -9667,10 +9657,21 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (shouldRelaunchLocked(changes, mTmpConfig)) {
// Aha, the activity isn't handling the change, so DIE DIE DIE.
configChangeFlags |= changes;
- startFreezingScreenLocked(globalChanges);
+ if (mVisible && mAtmService.mTmpUpdateConfigurationResult.mIsUpdating
+ && !mTransitionController.isShellTransitionsEnabled()) {
+ startFreezingScreenLocked(mAtmService.mTmpUpdateConfigurationResult.changes);
+ }
+ final boolean displayMayChange = mTmpConfig.windowConfiguration.getDisplayRotation()
+ != getWindowConfiguration().getDisplayRotation()
+ || !mTmpConfig.windowConfiguration.getMaxBounds().equals(
+ getWindowConfiguration().getMaxBounds());
+ final boolean isAppResizeOnly = !displayMayChange
+ && (changes & ~(CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE
+ | CONFIG_ORIENTATION | CONFIG_SCREEN_LAYOUT)) == 0;
// Do not preserve window if it is freezing screen because the original window won't be
// able to update drawn state that causes freeze timeout.
- preserveWindow &= isResizeOnlyChange(changes) && !mFreezingScreen;
+ // TODO(b/258618073): Always preserve if possible.
+ final boolean preserveWindow = isAppResizeOnly && !mFreezingScreen;
final boolean hasResizeChange = hasResizeChange(changes & ~info.getRealConfigChanged());
if (hasResizeChange) {
final boolean isDragResizing = task.isDragResizing();
@@ -9834,11 +9835,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return changes;
}
- private static boolean isResizeOnlyChange(int change) {
- return (change & ~(CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE | CONFIG_ORIENTATION
- | CONFIG_SCREEN_LAYOUT)) == 0;
- }
-
private static boolean hasResizeChange(int change) {
return (change & (CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE | CONFIG_ORIENTATION
| CONFIG_SCREEN_LAYOUT)) != 0;
@@ -9882,8 +9878,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
task.mTaskId, shortComponentName, Integer.toHexString(configChangeFlags));
}
- startFreezingScreenLocked(0);
-
try {
ProtoLog.i(WM_DEBUG_STATES, "Moving to %s Relaunching %s callers=%s" ,
(andResume ? "RESUMED" : "PAUSED"), this, Debug.getCallers(6));
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index cb2adbcf460a..d90d017a5570 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -73,7 +73,6 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLAS
import static com.android.server.wm.ActivityTaskManagerService.ANIMATE;
import static com.android.server.wm.ActivityTaskSupervisor.DEFER_RESUME;
import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_DEFAULT;
import static com.android.server.wm.BackgroundActivityStartController.BAL_BLOCK;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS;
@@ -1733,6 +1732,7 @@ class ActivityStarter {
// So disallow the transient hide activity to move itself to front, e.g. trampoline.
if (!avoidMoveToFront() && (mService.mHomeProcess == null
|| mService.mHomeProcess.mUid != realCallingUid)
+ && (prevTopTask != null && prevTopTask.isActivityTypeHomeOrRecents())
&& r.mTransitionController.isTransientHide(targetTask)) {
mCanMoveToFrontCode = MOVE_TO_FRONT_AVOID_LEGACY;
}
@@ -1859,8 +1859,7 @@ class ActivityStarter {
// over is removed.
// Passing {@code null} as the start parameter ensures all activities are made
// visible.
- mTargetRootTask.ensureActivitiesVisible(null /* starting */,
- 0 /* configChanges */, !PRESERVE_WINDOWS);
+ mTargetRootTask.ensureActivitiesVisible(null /* starting */);
// Go ahead and tell window manager to execute app transition for this activity
// since the app transition will not be triggered through the resume channel.
mTargetRootTask.mDisplayContent.executeAppTransition();
@@ -2867,7 +2866,7 @@ class ActivityStarter {
mRootWindowContainer.resumeFocusedTasksTopActivities(mTargetRootTask, null,
mOptions, mTransientLaunch);
} else {
- mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+ mRootWindowContainer.ensureActivitiesVisible();
}
} else {
ActivityOptions.abort(mOptions);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index dbae29bd37c9..f43c1b01e87c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -118,7 +118,6 @@ import static com.android.server.wm.ActivityTaskManagerService.H.REPORT_TIME_TRA
import static com.android.server.wm.ActivityTaskManagerService.UiHandler.DISMISS_DIALOG_UI_MSG;
import static com.android.server.wm.ActivityTaskSupervisor.DEFER_RESUME;
import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.ActivityTaskSupervisor.REMOVE_FROM_RECENTS;
import static com.android.server.wm.BackgroundActivityStartController.BalVerdict;
import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_DONT_LOCK;
@@ -496,16 +495,13 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
final UpdateConfigurationResult mTmpUpdateConfigurationResult =
new UpdateConfigurationResult();
+ // TODO(b/258618073): Remove this and make the related methods return whether config is changed.
static final class UpdateConfigurationResult {
// Configuration changes that were updated.
int changes;
// If the activity was relaunched to match the new configuration.
boolean activityRelaunched;
-
- void reset() {
- changes = 0;
- activityRelaunched = false;
- }
+ boolean mIsUpdating;
}
/** Current sequencing integer of the configuration, for skipping old configurations. */
@@ -3834,8 +3830,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
Settings.System.clearConfiguration(values);
}
updateConfigurationLocked(values, null, false, false /* persistent */,
- UserHandle.USER_NULL, false /* deferResume */,
- mTmpUpdateConfigurationResult);
+ UserHandle.USER_NULL, false /* deferResume */);
return mTmpUpdateConfigurationResult.changes != 0;
} finally {
Binder.restoreCallingIdentity(origId);
@@ -4507,12 +4502,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
}
- private boolean updateConfigurationLocked(Configuration values, ActivityRecord starting,
- boolean initLocale, boolean persistent, int userId, boolean deferResume) {
- return updateConfigurationLocked(values, starting, initLocale, persistent, userId,
- deferResume, null /* result */);
- }
-
/**
* Do either or both things: (1) change the current configuration, and (2)
* make sure the given activity is running with the (now) current
@@ -4524,8 +4513,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
* for that particular user
*/
boolean updateConfigurationLocked(Configuration values, ActivityRecord starting,
- boolean initLocale, boolean persistent, int userId, boolean deferResume,
- ActivityTaskManagerService.UpdateConfigurationResult result) {
+ boolean initLocale, boolean persistent, int userId, boolean deferResume) {
int changes = 0;
boolean kept = true;
@@ -4533,19 +4521,18 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
try {
if (values != null) {
changes = updateGlobalConfigurationLocked(values, initLocale, persistent, userId);
+ mTmpUpdateConfigurationResult.changes = changes;
+ mTmpUpdateConfigurationResult.mIsUpdating = true;
}
if (!deferResume) {
kept = ensureConfigAndVisibilityAfterUpdate(starting, changes);
}
} finally {
+ mTmpUpdateConfigurationResult.mIsUpdating = false;
continueWindowLayout();
}
-
- if (result != null) {
- result.changes = changes;
- result.activityRelaunched = !kept;
- }
+ mTmpUpdateConfigurationResult.activityRelaunched = !kept;
return kept;
}
@@ -5325,12 +5312,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
if (starting != null) {
- kept = starting.ensureActivityConfiguration(changes,
- false /* preserveWindow */);
+ kept = starting.ensureActivityConfiguration();
// And we need to make sure at this point that all other activities
// are made visible with the correct configuration.
- mRootWindowContainer.ensureActivitiesVisible(starting, changes,
- !PRESERVE_WINDOWS);
+ mRootWindowContainer.ensureActivitiesVisible(starting);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 10efb9491bed..1872f6e1fdb8 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1462,7 +1462,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
}
mLaunchingActivityWakeLock.release();
}
- mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+ mRootWindowContainer.ensureActivitiesVisible();
}
// Atomically retrieve all of the other things to do.
@@ -1603,7 +1603,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
*/
rootTask.cancelAnimation();
rootTask.setForceHidden(FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, true /* set */);
- rootTask.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+ rootTask.ensureActivitiesVisible(null /* starting */);
activityIdleInternal(null /* idleActivity */, false /* fromTimeout */,
true /* processPausingActivities */, null /* configuration */);
@@ -1622,7 +1622,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
// Follow on the workaround: activities are kept force hidden till the new windowing
// mode is set.
rootTask.setForceHidden(FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, false /* set */);
- mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+ mRootWindowContainer.ensureActivitiesVisible();
mRootWindowContainer.resumeFocusedTasksTopActivities();
} finally {
mService.continueWindowLayout();
@@ -2026,7 +2026,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
final Task rootTask = r.getRootTask();
if (rootTask.getDisplayArea().allResumedActivitiesComplete()) {
- mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+ mRootWindowContainer.ensureActivitiesVisible();
// Make sure activity & window visibility should be identical
// for all displays in this stage.
mRootWindowContainer.executeAppTransitionForAllDisplay();
@@ -2042,7 +2042,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
mRecentTasks.add(task);
mService.getTaskChangeNotificationController().notifyTaskStackChanged();
- rootTask.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+ rootTask.ensureActivitiesVisible(null /* starting */);
// When launching tasks behind, update the last active time of the top task after the new
// task has been shown briefly
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 68d13cd6789e..6ed896751bb3 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -91,6 +91,13 @@ class AsyncRotationController extends FadeAnimationController implements Consume
/** Non-zero if this controller is triggered by shell transition. */
private final @TransitionOp int mTransitionOp;
+ /**
+ * Whether {@link #setupStartTransaction} is called when the transition is ready.
+ * If this is never set for {@link #OP_CHANGE}, the display may be changed to original state
+ * before the transition is ready, then this controller should be finished.
+ */
+ private boolean mIsStartTransactionPrepared;
+
/** Whether the start transaction of the transition is committed (by shell). */
private boolean mIsStartTransactionCommitted;
@@ -226,7 +233,8 @@ class AsyncRotationController extends FadeAnimationController implements Consume
void updateTargetWindows() {
if (mTransitionOp == OP_LEGACY) return;
if (!mIsStartTransactionCommitted) {
- if (mTimeoutRunnable == null && !mDisplayContent.hasTopFixedRotationLaunchingApp()
+ if ((mTimeoutRunnable == null || !mIsStartTransactionPrepared)
+ && !mDisplayContent.hasTopFixedRotationLaunchingApp()
&& !mDisplayContent.isRotationChanging() && !mDisplayContent.inTransition()) {
Slog.d(TAG, "Cancel for no change");
mDisplayContent.finishAsyncRotationIfPossible();
@@ -401,9 +409,18 @@ class AsyncRotationController extends FadeAnimationController implements Consume
if (mTimeoutRunnable == null) {
mTimeoutRunnable = () -> {
synchronized (mService.mGlobalLock) {
- Slog.i(TAG, "Async rotation timeout: " + (!mIsStartTransactionCommitted
- ? " start transaction is not committed" : mTargetWindowTokens));
+ final String reason;
if (!mIsStartTransactionCommitted) {
+ if (!mIsStartTransactionPrepared) {
+ reason = "setupStartTransaction is not called";
+ } else {
+ reason = "start transaction is not committed";
+ }
+ } else {
+ reason = "unfinished windows " + mTargetWindowTokens;
+ }
+ Slog.i(TAG, "Async rotation timeout: " + reason);
+ if (!mIsStartTransactionCommitted && mIsStartTransactionPrepared) {
// The transaction commit timeout will be handled by:
// 1. BLASTSyncEngine will notify onTransactionCommitTimeout() and then
// apply the start transaction of transition.
@@ -558,6 +575,7 @@ class AsyncRotationController extends FadeAnimationController implements Consume
}
}
});
+ mIsStartTransactionPrepared = true;
}
/** Called when the start transition is ready, but it is not applied in time. */
@@ -577,6 +595,10 @@ class AsyncRotationController extends FadeAnimationController implements Consume
/** Called when the transition by shell is done. */
void onTransitionFinished() {
if (mTransitionOp == OP_CHANGE) {
+ if (mTargetWindowTokens.isEmpty()) {
+ // If nothing was handled, then complete with the transition.
+ mDisplayContent.finishAsyncRotationIfPossible();
+ }
// With screen rotation animation, the windows are always faded in when they are drawn.
// Because if they are drawn fast enough, the fade animation should not be observable.
return;
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index c3f1e41d4c5e..22d17b596c4c 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -1614,7 +1614,7 @@ class BackNavigationController {
"Setting Activity.mLauncherTaskBehind to true. Activity=%s", activity);
activity.mTaskSupervisor.mStoppingActivities.remove(activity);
activity.getDisplayContent().ensureActivitiesVisible(null /* starting */,
- 0 /* configChanges */, false /* preserveWindows */, true);
+ true /* notifyClients */);
}
private static void restoreLaunchBehind(@NonNull ActivityRecord activity) {
diff --git a/services/core/java/com/android/server/wm/ClientLifecycleManager.java b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
index 2e476772f85b..e4eb7b3a8144 100644
--- a/services/core/java/com/android/server/wm/ClientLifecycleManager.java
+++ b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
@@ -74,13 +74,13 @@ class ClientLifecycleManager {
}
/**
- * Similar to {@link #scheduleTransactionItem}, but is called without WM lock.
+ * Similar to {@link #scheduleTransactionItem}, but it sends the transaction immediately and
+ * it can be called without WM lock.
*
* @see WindowProcessController#setReportedProcState(int)
*/
- void scheduleTransactionItemUnlocked(@NonNull IApplicationThread client,
+ void scheduleTransactionItemNow(@NonNull IApplicationThread client,
@NonNull ClientTransactionItem transactionItem) throws RemoteException {
- // Immediately dispatching to client, and must not access WMS.
final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
if (transactionItem.isActivityLifecycleItem()) {
clientTransaction.setLifecycleStateRequest((ActivityLifecycleItem) transactionItem);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index f8dc9c79dda7..e7ecf520425d 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -779,7 +779,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
/**
* Used to prevent recursions when calling
- * {@link #ensureActivitiesVisible(ActivityRecord, int, boolean, boolean)}
+ * {@link #ensureActivitiesVisible(ActivityRecord, boolean)}
*/
private boolean mInEnsureActivitiesVisible = false;
@@ -1713,7 +1713,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
if (handled && requestingContainer instanceof ActivityRecord) {
final ActivityRecord activityRecord = (ActivityRecord) requestingContainer;
final boolean kept = updateDisplayOverrideConfigurationLocked(config, activityRecord,
- false /* deferResume */, null /* result */);
+ false /* deferResume */);
if (!kept) {
mRootWindowContainer.resumeFocusedTasksTopActivities();
}
@@ -1721,7 +1721,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// We have a new configuration to push so we need to update ATMS for now.
// TODO: Clean up display configuration push between ATMS and WMS after unification.
updateDisplayOverrideConfigurationLocked(config, null /* starting */,
- false /* deferResume */, null);
+ false /* deferResume */);
}
return handled;
}
@@ -6333,7 +6333,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
Settings.System.clearConfiguration(values);
updateDisplayOverrideConfigurationLocked(values, null /* starting */,
- false /* deferResume */, mAtmService.mTmpUpdateConfigurationResult);
+ false /* deferResume */);
return mAtmService.mTmpUpdateConfigurationResult.changes != 0;
}
@@ -6342,8 +6342,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
* new one will be computed in WM based on current display info.
*/
boolean updateDisplayOverrideConfigurationLocked(Configuration values,
- ActivityRecord starting, boolean deferResume,
- ActivityTaskManagerService.UpdateConfigurationResult result) {
+ ActivityRecord starting, boolean deferResume) {
int changes = 0;
boolean kept = true;
@@ -6361,19 +6360,19 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
} else {
changes = performDisplayOverrideConfigUpdate(values);
}
+ mAtmService.mTmpUpdateConfigurationResult.changes = changes;
+ mAtmService.mTmpUpdateConfigurationResult.mIsUpdating = true;
}
if (!deferResume) {
kept = mAtmService.ensureConfigAndVisibilityAfterUpdate(starting, changes);
}
} finally {
+ mAtmService.mTmpUpdateConfigurationResult.mIsUpdating = false;
mAtmService.continueWindowLayout();
}
- if (result != null) {
- result.changes = changes;
- result.activityRelaunched = !kept;
- }
+ mAtmService.mTmpUpdateConfigurationResult.activityRelaunched = !kept;
return kept;
}
@@ -6569,8 +6568,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
- void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
- boolean preserveWindows, boolean notifyClients) {
+ void ensureActivitiesVisible(ActivityRecord starting, boolean notifyClients) {
if (mInEnsureActivitiesVisible) {
// Don't do recursive work.
return;
@@ -6579,8 +6577,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
try {
mInEnsureActivitiesVisible = true;
forAllRootTasks(rootTask -> {
- rootTask.ensureActivitiesVisible(starting, configChanges, preserveWindows,
- notifyClients);
+ rootTask.ensureActivitiesVisible(starting, notifyClients);
});
if (mTransitionController.useShellTransitionsRotation()
&& mTransitionController.isCollecting()
@@ -6619,7 +6616,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
if (!wasTransitionSet) {
prepareAppTransition(TRANSIT_NONE);
}
- mRootWindowContainer.ensureActivitiesVisible(null, 0, false /* preserveWindows */);
+ mRootWindowContainer.ensureActivitiesVisible();
// If there was a transition set already we don't want to interfere with it as we might be
// starting it too early.
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index 9cc311dc6c8e..f40eb24fd5eb 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -33,8 +33,6 @@ class EnsureActivitiesVisibleHelper {
private boolean mAboveTop;
private boolean mContainerShouldBeVisible;
private boolean mBehindFullyOccludedContainer;
- private int mConfigChanges;
- private boolean mPreserveWindows;
private boolean mNotifyClients;
EnsureActivitiesVisibleHelper(TaskFragment container) {
@@ -45,14 +43,10 @@ class EnsureActivitiesVisibleHelper {
* Update all attributes except {@link mTaskFragment} to use in subsequent calculations.
*
* @param starting The activity that is being started
- * @param configChanges Parts of the configuration that changed for this activity for evaluating
- * if the screen should be frozen.
- * @param preserveWindows Flag indicating whether windows should be preserved when updating.
* @param notifyClients Flag indicating whether the configuration and visibility changes shoulc
* be sent to the clients.
*/
- void reset(ActivityRecord starting, int configChanges, boolean preserveWindows,
- boolean notifyClients) {
+ void reset(ActivityRecord starting, boolean notifyClients) {
mStarting = starting;
mTopRunningActivity = mTaskFragment.topRunningActivity();
// If the top activity is not fullscreen, then we need to make sure any activities under it
@@ -60,33 +54,26 @@ class EnsureActivitiesVisibleHelper {
mAboveTop = mTopRunningActivity != null;
mContainerShouldBeVisible = mTaskFragment.shouldBeVisible(mStarting);
mBehindFullyOccludedContainer = !mContainerShouldBeVisible;
- mConfigChanges = configChanges;
- mPreserveWindows = preserveWindows;
mNotifyClients = notifyClients;
}
/**
* Update and commit visibility with an option to also update the configuration of visible
* activities.
- * @see Task#ensureActivitiesVisible(ActivityRecord, int, boolean)
- * @see RootWindowContainer#ensureActivitiesVisible(ActivityRecord, int, boolean)
+ * @see Task#ensureActivitiesVisible(ActivityRecord)
+ * @see RootWindowContainer#ensureActivitiesVisible()
* @param starting The top most activity in the task.
* The activity is either starting or resuming.
* Caller should ensure starting activity is visible.
*
- * @param configChanges Parts of the configuration that changed for this activity for evaluating
- * if the screen should be frozen.
- * @param preserveWindows Flag indicating whether windows should be preserved when updating.
* @param notifyClients Flag indicating whether the configuration and visibility changes shoulc
* be sent to the clients.
*/
- void process(@Nullable ActivityRecord starting, int configChanges, boolean preserveWindows,
- boolean notifyClients) {
- reset(starting, configChanges, preserveWindows, notifyClients);
+ void process(@Nullable ActivityRecord starting, boolean notifyClients) {
+ reset(starting, notifyClients);
if (DEBUG_VISIBILITY) {
- Slog.v(TAG_VISIBILITY, "ensureActivitiesVisible behind " + mTopRunningActivity
- + " configChanges=0x" + Integer.toHexString(configChanges));
+ Slog.v(TAG_VISIBILITY, "ensureActivitiesVisible behind " + mTopRunningActivity);
}
if (mTopRunningActivity != null && mTaskFragment.asTask() != null) {
// TODO(14709632): Check if this needed to be implemented in TaskFragment.
@@ -107,8 +94,7 @@ class EnsureActivitiesVisibleHelper {
final TaskFragment childTaskFragment = child.asTaskFragment();
if (childTaskFragment != null
&& childTaskFragment.getTopNonFinishingActivity() != null) {
- childTaskFragment.updateActivityVisibilities(starting, configChanges,
- preserveWindows, notifyClients);
+ childTaskFragment.updateActivityVisibilities(starting, notifyClients);
// The TaskFragment should fully occlude the activities below if the bounds
// equals to its parent task, unless it is translucent.
mBehindFullyOccludedContainer |=
@@ -188,13 +174,11 @@ class EnsureActivitiesVisibleHelper {
// First: if this is not the current activity being started, make
// sure it matches the current configuration.
if (r != mStarting && mNotifyClients) {
- r.ensureActivityConfiguration(0 /* globalChanges */, mPreserveWindows,
- true /* ignoreVisibility */);
+ r.ensureActivityConfiguration(true /* ignoreVisibility */);
}
if (!r.attachedToProcess()) {
- makeVisibleAndRestartIfNeeded(mStarting, mConfigChanges,
- resumeTopActivity && isTop, r);
+ makeVisibleAndRestartIfNeeded(mStarting, resumeTopActivity && isTop, r);
} else if (r.isVisibleRequested()) {
// If this activity is already visible, then there is nothing to do here.
if (DEBUG_VISIBILITY) {
@@ -213,8 +197,6 @@ class EnsureActivitiesVisibleHelper {
} else {
r.makeVisibleIfNeeded(mStarting, mNotifyClients);
}
- // Aggregate current change flags.
- mConfigChanges |= r.configChangeFlags;
} else {
if (DEBUG_VISIBILITY) {
Slog.v(TAG_VISIBILITY, "Make invisible? " + r
@@ -242,16 +224,13 @@ class EnsureActivitiesVisibleHelper {
}
}
- private void makeVisibleAndRestartIfNeeded(ActivityRecord starting, int configChanges,
+ private void makeVisibleAndRestartIfNeeded(ActivityRecord starting,
boolean andResume, ActivityRecord r) {
// This activity needs to be visible, but isn't even running...
// get it started and resume if no other root task in this root task is resumed.
if (DEBUG_VISIBILITY) {
Slog.v(TAG_VISIBILITY, "Start and freeze screen for " + r);
}
- if (r != starting) {
- r.startFreezingScreenLocked(configChanges);
- }
if (!r.isVisibleRequested() || r.mLaunchTaskBehind) {
if (DEBUG_VISIBILITY) {
Slog.v(TAG_VISIBILITY, "Starting and making visible: " + r);
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 8cf471394c63..a84ebd95cf1c 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -167,10 +167,10 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal
/** {@inheritDoc} */
@Override
- public int interceptMotionBeforeQueueingNonInteractive(int displayId, long whenNanos,
- int policyFlags) {
+ public int interceptMotionBeforeQueueingNonInteractive(int displayId, int source, int action,
+ long whenNanos, int policyFlags) {
return mService.mPolicy.interceptMotionBeforeQueueingNonInteractive(
- displayId, whenNanos, policyFlags);
+ displayId, source, action, whenNanos, policyFlags);
}
/**
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index cbc7b836d250..6d1180497fed 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -43,7 +43,6 @@ import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
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.ActivityTaskSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.KeyguardControllerProto.AOD_SHOWING;
import static com.android.server.wm.KeyguardControllerProto.KEYGUARD_GOING_AWAY;
import static com.android.server.wm.KeyguardControllerProto.KEYGUARD_PER_DISPLAY;
@@ -239,7 +238,7 @@ class KeyguardController {
// Update the sleep token first such that ensureActivitiesVisible has correct sleep token
// state when evaluating visibilities.
updateKeyguardSleepToken();
- mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+ mRootWindowContainer.ensureActivitiesVisible();
InputMethodManagerInternal.get().updateImeWindowStatus(false /* disableImeIcon */,
displayId);
setWakeTransitionReady();
@@ -291,7 +290,7 @@ class KeyguardController {
// Some stack visibility might change (e.g. docked stack)
mRootWindowContainer.resumeFocusedTasksTopActivities();
- mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+ mRootWindowContainer.ensureActivitiesVisible();
mRootWindowContainer.addStartingWindowsForVisibleActivities();
mWindowManager.executeAppTransition();
} finally {
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 9305396caa19..97cc982b69dd 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -46,6 +46,7 @@ import static android.content.pm.ActivityInfo.screenOrientationToString;
import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9;
import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_APP_DEFAULT;
import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE;
import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN;
import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN;
@@ -1185,6 +1186,7 @@ final class LetterboxUiController {
mUserAspectRatio = getUserMinAspectRatioOverrideCode();
return mUserAspectRatio != USER_MIN_ASPECT_RATIO_UNSET
+ && mUserAspectRatio != USER_MIN_ASPECT_RATIO_APP_DEFAULT
&& mUserAspectRatio != USER_MIN_ASPECT_RATIO_FULLSCREEN;
}
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index 5269d35529bd..7b23004ae952 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -28,7 +28,6 @@ import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
import static com.android.server.wm.ActivityRecord.State.STOPPED;
import static com.android.server.wm.ActivityRecord.State.STOPPING;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION;
import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_TOP;
@@ -126,8 +125,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, OnRootTaskOrderChan
// The activity may be relaunched if it cannot handle the current configuration
// changes. The activity will be paused state if it is relaunched, otherwise it
// keeps the original stopped state.
- targetActivity.ensureActivityConfiguration(0 /* globalChanges */,
- false /* preserveWindow */, true /* ignoreVisibility */);
+ targetActivity.ensureActivityConfiguration(true /* ignoreVisibility */);
ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "Updated config=%s",
targetActivity.getConfiguration());
}
@@ -261,7 +259,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, OnRootTaskOrderChan
// If we updated the launch-behind state, update the visibility of the activities after
// we fetch the visible tasks to be controlled by the animation
- mService.mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+ mService.mRootWindowContainer.ensureActivitiesVisible();
ActivityOptions options = null;
if (eventTime > 0) {
@@ -380,8 +378,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, OnRootTaskOrderChan
// transition (the target activity will be one of closing apps).
if (!controller.shouldDeferCancelWithScreenshot()
&& !targetRootTask.isFocusedRootTaskOnDisplay()) {
- targetRootTask.ensureActivitiesVisible(null /* starting */,
- 0 /* starting */, false /* preserveWindows */);
+ targetRootTask.ensureActivitiesVisible(null /* starting */);
}
// Keep target root task in place, nothing changes, so ignore the transition
// logic below
@@ -389,7 +386,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, OnRootTaskOrderChan
}
mWindowManager.prepareAppTransitionNone();
- mService.mRootWindowContainer.ensureActivitiesVisible(null, 0, false);
+ mService.mRootWindowContainer.ensureActivitiesVisible();
mService.mRootWindowContainer.resumeFocusedTasksTopActivities();
// No reason to wait for the pausing activity in this case, as the hiding of
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 9a75dae3569e..ca66a66057a3 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -63,7 +63,6 @@ import static com.android.server.wm.ActivityTaskManagerService.TAG_SWITCH;
import static com.android.server.wm.ActivityTaskManagerService.isPip2ExperimentEnabled;
import static com.android.server.wm.ActivityTaskSupervisor.DEFER_RESUME;
import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.ActivityTaskSupervisor.dumpHistoryList;
import static com.android.server.wm.ActivityTaskSupervisor.printThisActivity;
import static com.android.server.wm.KeyguardController.KEYGUARD_SLEEP_TOKEN_TAG;
@@ -1753,8 +1752,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
// activities are affecting configuration now.
// Passing null here for 'starting' param value, so that visibility of actual starting
// activity will be properly updated.
- ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
- false /* preserveWindows */, false /* notifyClients */);
+ ensureActivitiesVisible(null /* starting */, false /* notifyClients */);
if (displayId == INVALID_DISPLAY) {
// The caller didn't provide a valid display id, skip updating config.
@@ -1778,7 +1776,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
if (displayContent != null) {
// Update the configuration of the activities on the display.
return displayContent.updateDisplayOverrideConfigurationLocked(config, starting,
- deferResume, null /* result */);
+ deferResume);
} else {
return true;
}
@@ -1865,16 +1863,18 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
* Make sure that all activities that need to be visible in the system actually are and update
* their configuration.
*/
- void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
- boolean preserveWindows) {
- ensureActivitiesVisible(starting, configChanges, preserveWindows, true /* notifyClients */);
+ void ensureActivitiesVisible() {
+ ensureActivitiesVisible(null /* starting */);
+ }
+
+ void ensureActivitiesVisible(ActivityRecord starting) {
+ ensureActivitiesVisible(starting, true /* notifyClients */);
}
/**
- * @see #ensureActivitiesVisible(ActivityRecord, int, boolean)
+ * @see #ensureActivitiesVisible()
*/
- void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
- boolean preserveWindows, boolean notifyClients) {
+ void ensureActivitiesVisible(ActivityRecord starting, boolean notifyClients) {
if (mTaskSupervisor.inActivityVisibilityUpdate()
|| mTaskSupervisor.isRootVisibilityUpdateDeferred()) {
// Don't do recursive work.
@@ -1885,8 +1885,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
// First the front root tasks. In case any are not fullscreen and are in front of home.
for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
final DisplayContent display = getChildAt(displayNdx);
- display.ensureActivitiesVisible(starting, configChanges, preserveWindows,
- notifyClients);
+ display.ensureActivitiesVisible(starting, notifyClients);
}
} finally {
mTaskSupervisor.endActivityVisibilityUpdate();
@@ -2237,7 +2236,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
try {
if (localVisibilityDeferred) {
mTaskSupervisor.setDeferRootVisibilityUpdate(false);
- ensureActivitiesVisible(null, 0, false /* preserveWindows */);
+ ensureActivitiesVisible();
}
} finally {
transitionController.continueTransitionReady();
@@ -2370,7 +2369,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
// It may be nothing to resume because there are pausing activities or all the top
// activities are resumed. Then it still needs to make sure all visible activities are
// running in case the tasks were reordered or there are non-top visible activities.
- ensureActivitiesVisible(null /* starting */, 0 /* configChanges */, !PRESERVE_WINDOWS);
+ ensureActivitiesVisible();
}
}
@@ -2542,8 +2541,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
// display orientation can be updated first if needed. Otherwise there may
// have redundant configuration changes due to apply outdated display
// orientation (from keyguard) to activity.
- rootTask.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
- false /* preserveWindows */);
+ rootTask.ensureActivitiesVisible(null /* starting */);
}
});
}
@@ -2885,8 +2883,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
if (allowDelay) {
result[0] &= task.goToSleepIfPossible(shuttingDown);
} else {
- task.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
- !PRESERVE_WINDOWS);
+ task.ensureActivitiesVisible(null /* starting */);
}
});
return result[0];
@@ -3774,8 +3771,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
}
}
if (!mHasActivityStarted) {
- ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
- false /* preserveWindows */);
+ ensureActivitiesVisible();
}
return mHasActivityStarted;
}
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 7995028b1ec7..ed54ea8229fe 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -771,6 +771,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
if (mLastReportedAnimatorScale != mService.getCurrentAnimatorScale()) {
mService.dispatchNewAnimatorScaleLocked(this);
}
+ mProcess.mWindowSession = this;
}
mAddedWindows.add(w);
}
@@ -782,6 +783,9 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
}
}
+ boolean hasWindow() {
+ return !mAddedWindows.isEmpty();
+ }
void onWindowSurfaceVisibilityChanged(WindowSurfaceController surfaceController,
boolean visible, int type) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index dbfcc22c6903..c674176e766d 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -90,7 +90,6 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLAS
import static com.android.server.wm.ActivityTaskManagerService.H.FIRST_ACTIVITY_TASK_MSG;
import static com.android.server.wm.ActivityTaskSupervisor.DEFER_RESUME;
import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.ActivityTaskSupervisor.REMOVE_FROM_RECENTS;
import static com.android.server.wm.ActivityTaskSupervisor.printThisActivity;
import static com.android.server.wm.IdentifierProto.HASH_CODE;
@@ -760,7 +759,7 @@ class Task extends TaskFragment {
return;
}
mResizeMode = resizeMode;
- mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+ mRootWindowContainer.ensureActivitiesVisible();
mRootWindowContainer.resumeFocusedTasksTopActivities();
updateTaskDescription();
}
@@ -801,15 +800,14 @@ class Task extends TaskFragment {
if (setBounds(bounds, forced) != BOUNDS_CHANGE_NONE) {
final ActivityRecord r = topRunningActivityLocked();
if (r != null) {
- kept = r.ensureActivityConfiguration(0 /* globalChanges */,
- preserveWindow);
+ kept = r.ensureActivityConfiguration();
// Preserve other windows for resizing because if resizing happens when there
// is a dialog activity in the front, the activity that still shows some
// content to the user will become black and cause flickers. Note in most cases
// this won't cause tons of irrelevant windows being preserved because only
// activities in this task may experience a bounds change. Configs for other
// activities stay the same.
- mRootWindowContainer.ensureActivitiesVisible(r, 0, preserveWindow);
+ mRootWindowContainer.ensureActivitiesVisible(r);
if (!kept) {
mRootWindowContainer.resumeFocusedTasksTopActivities();
}
@@ -915,7 +913,7 @@ class Task extends TaskFragment {
if (!deferResume) {
// The task might have already been running and its visibility needs to be synchronized
// with the visibility of the root task / windows.
- root.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+ root.ensureActivitiesVisible();
root.resumeFocusedTasksTopActivities();
}
@@ -4752,7 +4750,7 @@ class Task extends TaskFragment {
}
if (!mTaskSupervisor.isRootVisibilityUpdateDeferred()) {
- mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+ mRootWindowContainer.ensureActivitiesVisible();
mRootWindowContainer.resumeFocusedTasksTopActivities();
}
}
@@ -4793,8 +4791,7 @@ class Task extends TaskFragment {
mRootWindowContainer.resumeFocusedTasksTopActivities();
// Update visibility of activities before notifying WM. This way it won't try to resize
// windows that are no longer visible.
- mRootWindowContainer.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
- !PRESERVE_WINDOWS);
+ mRootWindowContainer.ensureActivitiesVisible();
}
final boolean isOnHomeDisplay() {
@@ -4938,41 +4935,27 @@ class Task extends TaskFragment {
* @param starting The top most activity in the task.
* The activity is either starting or resuming.
* Caller should ensure starting activity is visible.
- * @param preserveWindows Flag indicating whether windows should be preserved when updating
- * configuration in {@link EnsureActivitiesVisibleHelper}.
- * @param configChanges Parts of the configuration that changed for this activity for evaluating
- * if the screen should be frozen as part of
- * {@link EnsureActivitiesVisibleHelper}.
- *
*/
- void ensureActivitiesVisible(@Nullable ActivityRecord starting, int configChanges,
- boolean preserveWindows) {
- ensureActivitiesVisible(starting, configChanges, preserveWindows, true /* notifyClients */);
+ void ensureActivitiesVisible(@Nullable ActivityRecord starting) {
+ ensureActivitiesVisible(starting, true /* notifyClients */);
}
/**
* Ensure visibility with an option to also update the configuration of visible activities.
- * @see #ensureActivitiesVisible(ActivityRecord, int, boolean)
- * @see RootWindowContainer#ensureActivitiesVisible(ActivityRecord, int, boolean)
+ * @see #ensureActivitiesVisible(ActivityRecord)
+ * @see RootWindowContainer#ensureActivitiesVisible()
* @param starting The top most activity in the task.
* The activity is either starting or resuming.
* Caller should ensure starting activity is visible.
* @param notifyClients Flag indicating whether the visibility updates should be sent to the
* clients in {@link EnsureActivitiesVisibleHelper}.
- * @param preserveWindows Flag indicating whether windows should be preserved when updating
- * configuration in {@link EnsureActivitiesVisibleHelper}.
- * @param configChanges Parts of the configuration that changed for this activity for evaluating
- * if the screen should be frozen as part of
- * {@link EnsureActivitiesVisibleHelper}.
*/
// TODO: Should be re-worked based on the fact that each task as a root task in most cases.
- void ensureActivitiesVisible(@Nullable ActivityRecord starting, int configChanges,
- boolean preserveWindows, boolean notifyClients) {
+ void ensureActivitiesVisible(@Nullable ActivityRecord starting, boolean notifyClients) {
mTaskSupervisor.beginActivityVisibilityUpdate();
try {
forAllLeafTasks(task -> {
- task.updateActivityVisibilities(starting, configChanges, preserveWindows,
- notifyClients);
+ task.updateActivityVisibilities(starting, notifyClients);
}, true /* traverseTopToBottom */);
if (mTranslucentActivityWaiting != null &&
@@ -5273,7 +5256,7 @@ class Task extends TaskFragment {
// tell WindowManager that r is visible even though it is at the back of the root
// task.
r.setVisibility(true);
- ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+ ensureActivitiesVisible(null /* starting */);
// If launching behind, the app will start regardless of what's above it, so mark it
// as unknown even before prior `pause`. This also prevents a race between set-ready
// and activityPause. Launch-behind is basically only used for dream now.
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index c57983c53d37..90a3b25303f5 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -1777,13 +1777,11 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
void onRootTaskOrderChanged(Task rootTask);
}
- void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
- boolean preserveWindows, boolean notifyClients) {
+ void ensureActivitiesVisible(ActivityRecord starting, boolean notifyClients) {
mAtmService.mTaskSupervisor.beginActivityVisibilityUpdate();
try {
forAllRootTasks(rootTask -> {
- rootTask.ensureActivitiesVisible(starting, configChanges, preserveWindows,
- notifyClients);
+ rootTask.ensureActivitiesVisible(starting, notifyClients);
});
} finally {
mAtmService.mTaskSupervisor.endActivityVisibilityUpdate();
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 5d019122d52e..d425bdf5613f 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -57,7 +57,6 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_SWITC
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TRANSITION;
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.ActivityTaskSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.ActivityTaskSupervisor.printThisActivity;
import static com.android.server.wm.IdentifierProto.HASH_CODE;
import static com.android.server.wm.IdentifierProto.TITLE;
@@ -950,8 +949,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
}
if (shouldSleep) {
- updateActivityVisibilities(null /* starting */, 0 /* configChanges */,
- !PRESERVE_WINDOWS, true /* notifyClients */);
+ updateActivityVisibilities(null /* starting */, true /* notifyClients */);
}
return shouldSleep;
@@ -1218,12 +1216,11 @@ class TaskFragment extends WindowContainer<WindowContainer> {
return top != null && top.mLaunchTaskBehind;
}
- final void updateActivityVisibilities(@Nullable ActivityRecord starting, int configChanges,
- boolean preserveWindows, boolean notifyClients) {
+ final void updateActivityVisibilities(@Nullable ActivityRecord starting,
+ boolean notifyClients) {
mTaskSupervisor.beginActivityVisibilityUpdate();
try {
- mEnsureActivitiesVisibleHelper.process(
- starting, configChanges, preserveWindows, notifyClients);
+ mEnsureActivitiesVisibleHelper.process(starting, notifyClients);
} finally {
mTaskSupervisor.endActivityVisibilityUpdate();
}
@@ -1249,8 +1246,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
if (mResumedActivity == next && next.isState(RESUMED)
&& taskDisplayArea.allResumedActivitiesComplete()) {
// Ensure the visibility gets updated before execute app transition.
- taskDisplayArea.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
- false /* preserveWindows */, true /* notifyClients */);
+ taskDisplayArea.ensureActivitiesVisible(null /* starting */, true /* notifyClients */);
// Make sure we have executed any pending transitions, since there
// should be nothing left to do at this point.
executeAppTransition(options);
@@ -1907,7 +1903,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
prev.resumeKeyDispatchingLocked();
}
- mRootWindowContainer.ensureActivitiesVisible(resuming, 0, !PRESERVE_WINDOWS);
+ mRootWindowContainer.ensureActivitiesVisible(resuming);
// Notify when the task stack has changed, but only if visibilities changed (not just
// focus). Also if there is an active root pinned task - we always want to notify it about
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 3117db5f27f0..b12855e2bb49 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1136,8 +1136,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
// The transient hide tasks could be occluded now, e.g. returning to home. So trigger
// the update to make the activities in the tasks invisible-requested, then the next
// step can continue to commit the visibility.
- mController.mAtm.mRootWindowContainer.ensureActivitiesVisible(null /* starting */,
- 0 /* configChanges */, true /* preserveWindows */);
+ mController.mAtm.mRootWindowContainer.ensureActivitiesVisible();
// Record all the now-hiding activities so that they are committed. Just use
// mParticipants because we can avoid a new list this way.
for (int i = 0; i < mTransientHideTasks.size(); ++i) {
@@ -2863,8 +2862,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
* check whether to deliver the new configuration to clients.
*/
@Nullable
- ArrayList<ActivityRecord> applyDisplayChangeIfNeeded() {
- ArrayList<ActivityRecord> activitiesMayChange = null;
+ void applyDisplayChangeIfNeeded(@NonNull ArraySet<WindowContainer<?>> activitiesMayChange) {
for (int i = mParticipants.size() - 1; i >= 0; --i) {
final WindowContainer<?> wc = mParticipants.valueAt(i);
final DisplayContent dc = wc.asDisplayContent();
@@ -2881,18 +2879,13 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
// If the update is deferred, sendNewConfiguration won't deliver new configuration to
// clients, then it is the caller's responsibility to deliver the changes.
if (mController.mAtm.mTaskSupervisor.isRootVisibilityUpdateDeferred()) {
- if (activitiesMayChange == null) {
- activitiesMayChange = new ArrayList<>();
- }
- final ArrayList<ActivityRecord> visibleActivities = activitiesMayChange;
dc.forAllActivities(r -> {
if (r.isVisibleRequested()) {
- visibleActivities.add(r);
+ activitiesMayChange.add(r);
}
});
}
}
- return activitiesMayChange;
}
boolean getLegacyIsReady() {
diff --git a/services/core/java/com/android/server/wm/WindowManagerFlags.java b/services/core/java/com/android/server/wm/WindowManagerFlags.java
index 89a70e502415..7b0d931abf8a 100644
--- a/services/core/java/com/android/server/wm/WindowManagerFlags.java
+++ b/services/core/java/com/android/server/wm/WindowManagerFlags.java
@@ -43,8 +43,6 @@ class WindowManagerFlags {
/* Start Available Flags */
- final boolean mWindowStateResizeItemFlag = Flags.windowStateResizeItemFlag();
-
final boolean mWallpaperOffsetAsync = Flags.wallpaperOffsetAsync();
final boolean mAllowsScreenSizeDecoupledFromStatusBarAndCutout =
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c1310a6880fd..dda33f3f501b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3105,7 +3105,7 @@ public class WindowManagerService extends IWindowManager.Stub
try {
synchronized (mGlobalLock) {
if (mAtmService.mKeyguardController.isKeyguardShowing(DEFAULT_DISPLAY)) {
- mRoot.ensureActivitiesVisible(null, 0, false /* preserveWindows */);
+ mRoot.ensureActivitiesVisible();
}
}
} finally {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 4b99432b2943..9e4a31c3773a 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -65,7 +65,6 @@ import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
import static com.android.server.wm.ActivityRecord.State.PAUSING;
import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
-import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.ActivityTaskSupervisor.REMOVE_FROM_RECENTS;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
@@ -571,14 +570,15 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
mService.deferWindowLayout();
mService.mTaskSupervisor.setDeferRootVisibilityUpdate(true /* deferUpdate */);
try {
- final ArrayList<ActivityRecord> activitiesMayChange =
- transition != null ? transition.applyDisplayChangeIfNeeded() : null;
- if (activitiesMayChange != null) {
- effects |= TRANSACT_EFFECTS_CLIENT_CONFIG;
+ final ArraySet<WindowContainer<?>> haveConfigChanges = new ArraySet<>();
+ if (transition != null) {
+ transition.applyDisplayChangeIfNeeded(haveConfigChanges);
+ if (!haveConfigChanges.isEmpty()) {
+ effects |= TRANSACT_EFFECTS_CLIENT_CONFIG;
+ }
}
final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps();
final int hopSize = hops.size();
- final ArraySet<WindowContainer<?>> haveConfigChanges = new ArraySet<>();
Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries =
t.getChanges().entrySet().iterator();
while (entries.hasNext()) {
@@ -626,7 +626,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
// When removing pip, make sure that onStop is sent to the app ahead of
// onPictureInPictureModeChanged.
// See also PinnedStackTests#testStopBeforeMultiWindowCallbacksOnDismiss
- wc.asTask().ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+ wc.asTask().ensureActivitiesVisible(null /* starting */);
wc.asTask().mTaskSupervisor.processStoppingAndFinishingActivities(
null /* launchedActivity */, false /* processPausingActivities */,
"force-stop-on-removing-pip");
@@ -692,29 +692,16 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) {
mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */);
// Already calls ensureActivityConfig
- mService.mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+ mService.mRootWindowContainer.ensureActivitiesVisible();
mService.mRootWindowContainer.resumeFocusedTasksTopActivities();
} else if ((effects & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {
for (int i = haveConfigChanges.size() - 1; i >= 0; --i) {
haveConfigChanges.valueAt(i).forAllActivities(r -> {
- r.ensureActivityConfiguration(0, PRESERVE_WINDOWS);
- if (activitiesMayChange != null) {
- activitiesMayChange.remove(r);
+ if (r.isVisibleRequested()) {
+ r.ensureActivityConfiguration(true /* ignoreVisibility */);
}
});
}
- // TODO(b/258618073): Combine with haveConfigChanges after confirming that there
- // is no problem to always preserve window. Currently this uses the parameters
- // as ATMS#ensureConfigAndVisibilityAfterUpdate.
- if (activitiesMayChange != null) {
- for (int i = activitiesMayChange.size() - 1; i >= 0; --i) {
- final ActivityRecord ar = activitiesMayChange.get(i);
- if (!ar.isVisibleRequested()) continue;
- ar.ensureActivityConfiguration(0 /* globalChanges */,
- !PRESERVE_WINDOWS, true /* ignoreVisibility */,
- false /* isRequestedOrientationChanged */);
- }
- }
}
if (effects != 0) {
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 5721750fbf63..b8fa5e5b2786 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -188,6 +188,10 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
// Set to true when process was launched with a wrapper attached
private volatile boolean mUsingWrapper;
+ /** Non-null if this process may have a window. */
+ @Nullable
+ Session mWindowSession;
+
// Thread currently set for VR scheduling
int mVrThreadTid;
@@ -399,7 +403,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
// the latest configuration in their lifecycle callbacks (e.g. onReceive, onCreate).
try {
// No WM lock here.
- mAtm.getLifecycleManager().scheduleTransactionItemUnlocked(
+ mAtm.getLifecycleManager().scheduleTransactionItemNow(
thread, configurationChangeItem);
} catch (Exception e) {
Slog.e(TAG_CONFIGURATION, "Failed to schedule ConfigurationChangeItem="
@@ -989,7 +993,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
if (packageName.equals(r.packageName)
&& r.applyAppSpecificConfig(nightMode, localesOverride, gender)
&& r.isVisibleRequested()) {
- r.ensureActivityConfiguration(0 /* globalChanges */, true /* preserveWindow */);
+ r.ensureActivityConfiguration();
}
}
}
@@ -1675,7 +1679,12 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
private void scheduleClientTransactionItem(@NonNull IApplicationThread thread,
@NonNull ClientTransactionItem transactionItem) {
try {
- mAtm.getLifecycleManager().scheduleTransactionItem(thread, transactionItem);
+ if (mWindowSession != null && mWindowSession.hasWindow()) {
+ mAtm.getLifecycleManager().scheduleTransactionItem(thread, transactionItem);
+ } else {
+ // Non-UI process can handle the change directly.
+ mAtm.getLifecycleManager().scheduleTransactionItemNow(thread, transactionItem);
+ }
} catch (DeadObjectException e) {
// Expected if the process has been killed.
Slog.w(TAG_CONFIGURATION, "Failed for dead process. ClientTransactionItem="
@@ -1723,7 +1732,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
overrideConfig.assetsSeq = assetSeq;
r.onRequestedOverrideConfigurationChanged(overrideConfig);
if (r.isVisibleRequested()) {
- r.ensureActivityConfiguration(0, true);
+ r.ensureActivityConfiguration();
}
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index ec4bdf91bcca..58ade1bf5c1e 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -252,6 +252,7 @@ import com.android.server.policy.WindowManagerPolicy;
import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
import com.android.server.wm.RefreshRatePolicy.FrameRateVote;
import com.android.server.wm.SurfaceAnimator.AnimationType;
+import com.android.window.flags.Flags;
import dalvik.annotation.optimization.NeverCompile;
@@ -3675,7 +3676,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
markRedrawForSyncReported();
- if (mWmService.mFlags.mWindowStateResizeItemFlag) {
+ if (Flags.bundleClientTransactionFlag()) {
getProcess().scheduleClientTransactionItem(
WindowStateResizeItem.obtain(mClient, mClientWindowFrames, reportDraw,
mLastReportedConfiguration, getCompatInsetsState(), forceRelayout,
@@ -5637,6 +5638,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// Skip sync for invisible app windows which are not managed by activity lifecycle.
return false;
}
+ if (mActivityRecord != null && mViewVisibility != View.VISIBLE
+ && mWinAnimator.mAttrType != TYPE_BASE_APPLICATION
+ && mWinAnimator.mAttrType != TYPE_APPLICATION_STARTING) {
+ // Skip sync for invisible app windows which are not managed by activity lifecycle.
+ return false;
+ }
// In the WindowContainer implementation we immediately mark ready
// since a generic WindowContainer only needs to wait for its
// children to finish and is immediately ready from its own
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 0dd0564ae034..9ba0a2aae02c 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -358,8 +358,8 @@ public:
void notifyVibratorState(int32_t deviceId, bool isOn) override;
bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) override;
void interceptKeyBeforeQueueing(const KeyEvent& keyEvent, uint32_t& policyFlags) override;
- void interceptMotionBeforeQueueing(int32_t displayId, nsecs_t when,
- uint32_t& policyFlags) override;
+ void interceptMotionBeforeQueueing(int32_t displayId, uint32_t source, int32_t action,
+ nsecs_t when, uint32_t& policyFlags) override;
nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>& token, const KeyEvent& keyEvent,
uint32_t policyFlags) override;
std::optional<KeyEvent> dispatchUnhandledKey(const sp<IBinder>& token, const KeyEvent& keyEvent,
@@ -1496,7 +1496,8 @@ void NativeInputManager::interceptKeyBeforeQueueing(const KeyEvent& keyEvent,
handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
}
-void NativeInputManager::interceptMotionBeforeQueueing(int32_t displayId, nsecs_t when,
+void NativeInputManager::interceptMotionBeforeQueueing(int32_t displayId, uint32_t source,
+ int32_t action, nsecs_t when,
uint32_t& policyFlags) {
ATRACE_CALL();
// Policy:
@@ -1525,7 +1526,7 @@ void NativeInputManager::interceptMotionBeforeQueueing(int32_t displayId, nsecs_
const jint wmActions =
env->CallIntMethod(mServiceObj,
gServiceClassInfo.interceptMotionBeforeQueueingNonInteractive,
- displayId, when, policyFlags);
+ displayId, source, action, when, policyFlags);
if (checkAndClearExceptionFromCallback(env, "interceptMotionBeforeQueueingNonInteractive")) {
return;
}
@@ -2943,7 +2944,7 @@ int register_android_server_InputManager(JNIEnv* env) {
"interceptKeyBeforeQueueing", "(Landroid/view/KeyEvent;I)I");
GET_METHOD_ID(gServiceClassInfo.interceptMotionBeforeQueueingNonInteractive, clazz,
- "interceptMotionBeforeQueueingNonInteractive", "(IJI)I");
+ "interceptMotionBeforeQueueingNonInteractive", "(IIIJI)I");
GET_METHOD_ID(gServiceClassInfo.interceptKeyBeforeDispatching, clazz,
"interceptKeyBeforeDispatching",
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 59e95e7571d0..1185a4e4f93b 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2734,11 +2734,7 @@ public final class SystemServer implements Dumpable {
// AdServicesManagerService (PP API service)
t.traceBegin("StartAdServicesManagerService");
- SystemService adServices = mSystemServiceManager
- .startService(AD_SERVICES_MANAGER_SERVICE_CLASS);
- if (adServices instanceof Dumpable) {
- mDumper.addDumpable((Dumpable) adServices);
- }
+ mSystemServiceManager.startService(AD_SERVICES_MANAGER_SERVICE_CLASS);
t.traceEnd();
// OnDevicePersonalizationSystemService
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerStateTest.kt b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerStateTest.kt
index 49fa2545e001..dafbbb3e0140 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerStateTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerStateTest.kt
@@ -23,8 +23,10 @@ import org.junit.Rule
import org.junit.Test
import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
+import java.util.concurrent.Executor
@SmallTest
class DisplayPowerStateTest {
@@ -36,15 +38,21 @@ class DisplayPowerStateTest {
private val mockBlanker = mock<DisplayBlanker>()
private val mockColorFade = mock<ColorFade>()
+ private val mockExecutor = mock<Executor>()
@Before
fun setUp() {
- displayPowerState = DisplayPowerState(mockBlanker, mockColorFade, 123, Display.STATE_ON)
+ displayPowerState = DisplayPowerState(mockBlanker, mockColorFade, 123, Display.STATE_ON,
+ mockExecutor)
}
@Test
fun `destroys ColorFade on stop`() {
displayPowerState.stop()
+ val runnableCaptor = argumentCaptor<Runnable>()
+
+ verify(mockExecutor).execute(runnableCaptor.capture())
+ runnableCaptor.firstValue.run()
verify(mockColorFade).destroy()
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
index 8d8274c61b20..87fc7a484c5c 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
@@ -122,6 +122,16 @@ public class HdrClamperTest {
}
@Test
+ public void testRegisterHdrListener_ZeroMinHdrPercent() {
+ IBinder otherBinder = mock(IBinder.class);
+ mHdrClamper.resetHdrConfig(TEST_HDR_DATA, WIDTH, HEIGHT,
+ /* minimumHdrPercentOfScreen= */ 0, otherBinder);
+
+ verify(mMockHdrInfoListener).unregister(mMockBinder);
+ verify(mMockHdrInfoListener).register(otherBinder);
+ }
+
+ @Test
public void testRegisterNotCalledIfHbmConfigIsMissing() {
IBinder otherBinder = mock(IBinder.class);
mHdrClamper.resetHdrConfig(TEST_HDR_DATA, WIDTH, HEIGHT, -1, otherBinder);
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index 293003dcda18..32878b3e199f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -67,6 +67,7 @@ import android.location.LocationManagerInternal;
import android.location.LocationManagerInternal.ProviderEnabledListener;
import android.location.LocationRequest;
import android.location.LocationResult;
+import android.location.flags.Flags;
import android.location.provider.IProviderRequestListener;
import android.location.provider.ProviderProperties;
import android.location.provider.ProviderRequest;
@@ -78,8 +79,10 @@ import android.os.PackageTagsList;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.WorkSource;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.util.Log;
@@ -97,6 +100,7 @@ import com.android.server.location.injector.TestInjector;
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;
@@ -140,6 +144,9 @@ public class LocationProviderManagerTest {
private static final WorkSource WORK_SOURCE = new WorkSource(IDENTITY.getUid());
private static final String MISSING_PERMISSION = "missing_permission";
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private Random mRandom;
@Mock
@@ -1347,6 +1354,24 @@ public class LocationProviderManagerTest {
assertThat(mManager.isVisibleToCaller()).isFalse();
}
+ @Test
+ public void testValidateLocation_futureLocation() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_LOCATION_VALIDATION);
+ Location location = createLocation(NAME, mRandom);
+ mProvider.setProviderLocation(location);
+
+ assertThat(mPassive.getLastLocation(new LastLocationRequest.Builder().build(), IDENTITY,
+ PERMISSION_FINE)).isEqualTo(location);
+
+ Location futureLocation = createLocation(NAME, mRandom);
+ futureLocation.setElapsedRealtimeNanos(
+ SystemClock.elapsedRealtimeNanos() + TimeUnit.SECONDS.toNanos(2));
+ mProvider.setProviderLocation(futureLocation);
+
+ assertThat(mPassive.getLastLocation(new LastLocationRequest.Builder().build(), IDENTITY,
+ PERMISSION_FINE)).isEqualTo(location);
+ }
+
@MediumTest
@Test
public void testEnableMsl_expectedBehavior() throws Exception {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
index 6c44fd0da1c4..60cedcfd6dd0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
@@ -44,6 +44,7 @@ import org.junit.runners.JUnit4;
import org.mockito.ArgumentCaptor;
import java.util.ArrayList;
+import java.util.function.BiFunction;
/**
* A unit test for PackageMonitorCallbackHelper implementation.
@@ -78,7 +79,8 @@ public class PackageMonitorCallbackHelperTest {
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
- null /* instantUserIds */, null /* broadcastAllowList */, mHandler);
+ null /* instantUserIds */, null /* broadcastAllowList */, mHandler,
+ null /* filterExtras */);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
}
@@ -91,7 +93,7 @@ public class PackageMonitorCallbackHelperTest {
Binder.getCallingUid());
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0}, null /* instantUserIds */,
- null /* broadcastAllowList */, mHandler);
+ null /* broadcastAllowList */, mHandler, null /* filterExtras */);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any());
@@ -99,12 +101,41 @@ public class PackageMonitorCallbackHelperTest {
mPackageMonitorCallbackHelper.unregisterPackageMonitorCallback(callback);
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
- null /* instantUserIds */, null /* broadcastAllowList */, mHandler);
+ null /* instantUserIds */, null /* broadcastAllowList */, mHandler,
+ null /* filterExtras */);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
}
@Test
+ public void testPackageMonitorCallback_SuspendCallbackCalled() throws Exception {
+ Bundle result = new Bundle();
+ result.putInt(Intent.EXTRA_UID, FAKE_PACKAGE_UID);
+ result.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, new String[]{FAKE_PACKAGE_NAME});
+ BiFunction<Integer, Bundle, Bundle> filterExtras = (callingUid, intentExtras) -> result;
+
+ IRemoteCallback callback = createMockPackageMonitorCallback();
+ mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */,
+ Binder.getCallingUid());
+ mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGES_SUSPENDED,
+ FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0}, null /* instantUserIds */,
+ null /* broadcastAllowList */, mHandler, filterExtras);
+
+ ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+ verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(
+ bundleCaptor.capture());
+ Bundle bundle = bundleCaptor.getValue();
+ Intent intent = bundle.getParcelable(
+ PackageManager.EXTRA_PACKAGE_MONITOR_CALLBACK_RESULT, Intent.class);
+ assertThat(intent).isNotNull();
+ assertThat(intent.getAction()).isEqualTo(Intent.ACTION_PACKAGES_SUSPENDED);
+ String[] pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ assertThat(pkgList).isNotNull();
+ assertThat(pkgList.length).isEqualTo(1);
+ assertThat(pkgList[0]).isEqualTo(FAKE_PACKAGE_NAME);
+ }
+
+ @Test
public void testRegisterPackageMonitorCallback_callbackCalled() throws Exception {
IRemoteCallback callback = createMockPackageMonitorCallback();
@@ -112,7 +143,8 @@ public class PackageMonitorCallbackHelperTest {
Binder.getCallingUid());
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
- null /* instantUserIds */, null /* broadcastAllowList */, mHandler);
+ null /* instantUserIds */, null /* broadcastAllowList */, mHandler,
+ null /* filterExtras */);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(
@@ -136,7 +168,8 @@ public class PackageMonitorCallbackHelperTest {
// Notify for user 10
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{10} /* userIds */,
- null /* instantUserIds */, null /* broadcastAllowList */, mHandler);
+ null /* instantUserIds */, null /* broadcastAllowList */, mHandler,
+ null /* filterExtras */);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
}
@@ -239,7 +272,8 @@ public class PackageMonitorCallbackHelperTest {
mPackageMonitorCallbackHelper.onUserRemoved(10);
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{10} /* userIds */,
- null /* instantUserIds */, null /* broadcastAllowList */, mHandler);
+ null /* instantUserIds */, null /* broadcastAllowList */, mHandler,
+ null /* filterExtras */);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
}
@@ -255,7 +289,7 @@ public class PackageMonitorCallbackHelperTest {
Binder.getCallingUid());
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
- null /* instantUserIds */, broadcastAllowList, mHandler);
+ null /* instantUserIds */, broadcastAllowList, mHandler, null /* filterExtras */);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any());
}
@@ -271,7 +305,7 @@ public class PackageMonitorCallbackHelperTest {
Binder.getCallingUid());
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
- null /* instantUserIds */, broadcastAllowList, mHandler);
+ null /* instantUserIds */, broadcastAllowList, mHandler, null /* filterExtras */);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
}
@@ -287,7 +321,7 @@ public class PackageMonitorCallbackHelperTest {
Process.SYSTEM_UID);
mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
- null /* instantUserIds */, broadcastAllowList, mHandler);
+ null /* instantUserIds */, broadcastAllowList, mHandler, null /* filterExtras */);
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any());
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
index c9e1c4a8bfc5..3aaac2e9cf1b 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
@@ -16,21 +16,30 @@
package com.android.server.biometrics.sensors.face;
+import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
import static android.hardware.face.FaceSensorProperties.TYPE_UNKNOWN;
+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.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.SensorProps;
+import android.hardware.face.FaceAuthenticateOptions;
import android.hardware.face.FaceSensorConfigurations;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
+import android.hardware.face.IFaceServiceReceiver;
+import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.RequiresFlagsEnabled;
@@ -42,6 +51,7 @@ import android.testing.TestableContext;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.internal.R;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.server.biometrics.Flags;
@@ -66,6 +76,7 @@ public class FaceServiceTest {
private static final int ID_VIRTUAL = 6;
private static final String NAME_DEFAULT = "default";
private static final String NAME_VIRTUAL = "virtual";
+ private static final String OP_PACKAGE_NAME = "FaceServiceTest/SystemUi";
@Rule
public final MockitoRule mMockito = MockitoJUnit.rule();
@@ -78,15 +89,19 @@ public class FaceServiceTest {
@Rule
public final FakeSettingsProviderRule mSettingsRule = FakeSettingsProvider.rule();
@Mock
- FaceProvider mFaceProviderDefault;
+ private FaceProvider mFaceProviderDefault;
+ @Mock
+ private FaceProvider mFaceProviderVirtual;
+ @Mock
+ private IFace mDefaultFaceDaemon;
@Mock
- FaceProvider mFaceProviderVirtual;
+ private IFace mVirtualFaceDaemon;
@Mock
- IFace mDefaultFaceDaemon;
+ private IBiometricService mIBiometricService;
@Mock
- IFace mVirtualFaceDaemon;
+ private IBinder mToken;
@Mock
- IBiometricService mIBiometricService;
+ private IFaceServiceReceiver mFaceServiceReceiver;
private final SensorProps mDefaultSensorProps = new SensorProps();
private final SensorProps mVirtualSensorProps = new SensorProps();
@@ -117,7 +132,13 @@ public class FaceServiceTest {
new SensorProps[]{mVirtualSensorProps});
when(mFaceProviderDefault.getSensorProperties()).thenReturn(List.of(mSensorPropsDefault));
when(mFaceProviderVirtual.getSensorProperties()).thenReturn(List.of(mSensorPropsVirtual));
+ when(mFaceProviderDefault.containsSensor(anyInt()))
+ .thenAnswer(i -> i.getArguments()[0].equals(ID_DEFAULT));
+ when(mFaceProviderVirtual.containsSensor(anyInt()))
+ .thenAnswer(i -> i.getArguments()[0].equals(ID_VIRTUAL));
+ mContext.getTestablePermissions().setPermission(
+ USE_BIOMETRIC_INTERNAL, PackageManager.PERMISSION_GRANTED);
mFaceSensorConfigurations = new FaceSensorConfigurations(false);
mFaceSensorConfigurations.addAidlConfigs(new String[]{NAME_DEFAULT, NAME_VIRTUAL},
(name) -> {
@@ -136,7 +157,13 @@ public class FaceServiceTest {
if (NAME_DEFAULT.equals(filteredSensorProps.first)) return mFaceProviderDefault;
if (NAME_VIRTUAL.equals(filteredSensorProps.first)) return mFaceProviderVirtual;
return null;
- }, () -> mIBiometricService);
+ }, () -> mIBiometricService,
+ (name) -> {
+ if (NAME_DEFAULT.equals(name)) return mFaceProviderDefault;
+ if (NAME_VIRTUAL.equals(name)) return mFaceProviderVirtual;
+ return null;
+ },
+ () -> new String[]{NAME_DEFAULT, NAME_VIRTUAL});
}
@Test
@@ -191,6 +218,39 @@ public class FaceServiceTest {
eq(Utils.propertyStrengthToAuthenticatorStrength(STRENGTH_STRONG)), any());
}
+ @Test
+ public void testOptionsForAuthentication() throws Exception {
+ FaceAuthenticateOptions faceAuthenticateOptions = new FaceAuthenticateOptions.Builder()
+ .build();
+ initService();
+ mFaceService.mServiceWrapper.registerAuthenticators(List.of());
+ waitForRegistration();
+
+ final long operationId = 5;
+ mFaceService.mServiceWrapper.authenticate(mToken, operationId, mFaceServiceReceiver,
+ faceAuthenticateOptions);
+
+ assertThat(faceAuthenticateOptions.getSensorId()).isEqualTo(ID_DEFAULT);
+ }
+
+ @Test
+ public void testOptionsForDetect() throws Exception {
+ FaceAuthenticateOptions faceAuthenticateOptions = new FaceAuthenticateOptions.Builder()
+ .setOpPackageName(ComponentName.unflattenFromString(OP_PACKAGE_NAME)
+ .getPackageName())
+ .build();
+ mContext.getOrCreateTestableResources().addOverride(
+ R.string.config_keyguardComponent,
+ OP_PACKAGE_NAME);
+ initService();
+ mFaceService.mServiceWrapper.registerAuthenticators(List.of());
+ waitForRegistration();
+ mFaceService.mServiceWrapper.detectFace(mToken, mFaceServiceReceiver,
+ faceAuthenticateOptions);
+
+ assertThat(faceAuthenticateOptions.getSensorId()).isEqualTo(ID_DEFAULT);
+ }
+
private void waitForRegistration() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
mFaceService.mServiceWrapper.addAuthenticatorsRegisteredCallback(
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
index f570ba23441d..88956b614eae 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
@@ -40,6 +40,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.AppOpsManager;
+import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.fingerprint.IFingerprint;
@@ -61,6 +62,7 @@ import android.testing.TestableContext;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.internal.R;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.server.LocalServices;
@@ -92,6 +94,7 @@ public class FingerprintServiceTest {
private static final String NAME_VIRTUAL = "virtual";
private static final List<FingerprintSensorPropertiesInternal> HIDL_AUTHENTICATORS =
List.of();
+ private static final String OP_PACKAGE_NAME = "FingerprintServiceTest/SystemUi";
@Rule
public final MockitoRule mMockito = MockitoJUnit.rule();
@@ -343,6 +346,24 @@ public class FingerprintServiceTest {
assertEquals((int) (uidCaptor.getValue()), Binder.getCallingUid());
}
+ @Test
+ public void testOptionsForDetect() throws Exception {
+ FingerprintAuthenticateOptions fingerprintAuthenticateOptions =
+ new FingerprintAuthenticateOptions.Builder()
+ .setOpPackageName(ComponentName.unflattenFromString(
+ OP_PACKAGE_NAME).getPackageName())
+ .build();
+
+ mContext.getOrCreateTestableResources().addOverride(
+ R.string.config_keyguardComponent,
+ OP_PACKAGE_NAME);
+ initServiceWithAndWait(NAME_DEFAULT);
+ mService.mServiceWrapper.detectFingerprint(mToken, mServiceReceiver,
+ fingerprintAuthenticateOptions);
+
+ assertThat(fingerprintAuthenticateOptions.getSensorId()).isEqualTo(ID_DEFAULT);
+ }
+
private FingerprintAuthenticateOptions verifyAuthenticateWithNewRequestId(
FingerprintProvider provider, long operationId) {
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
index 071d571adabe..9b28b817a1b9 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
@@ -53,11 +53,11 @@ import java.util.List;
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class VirtualCameraControllerTest {
- private static final int CAMERA_DISPLAY_NAME_RES_ID_1 = 10;
+ private static final String CAMERA_NAME_1 = "Virtual camera 1";
private static final int CAMERA_WIDTH_1 = 100;
private static final int CAMERA_HEIGHT_1 = 200;
- private static final int CAMERA_DISPLAY_NAME_RES_ID_2 = 11;
+ private static final String CAMERA_NAME_2 = "Virtual camera 2";
private static final int CAMERA_WIDTH_2 = 400;
private static final int CAMERA_HEIGHT_2 = 600;
private static final int CAMERA_FORMAT = ImageFormat.YUV_420_888;
@@ -84,7 +84,7 @@ public class VirtualCameraControllerTest {
@Test
public void registerCamera_registersCamera() throws Exception {
mVirtualCameraController.registerCamera(createVirtualCameraConfig(
- CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_DISPLAY_NAME_RES_ID_1));
+ CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_NAME_1));
ArgumentCaptor<VirtualCameraConfiguration> configurationCaptor =
ArgumentCaptor.forClass(VirtualCameraConfiguration.class);
@@ -98,7 +98,7 @@ public class VirtualCameraControllerTest {
@Test
public void unregisterCamera_unregistersCamera() throws Exception {
VirtualCameraConfig config = createVirtualCameraConfig(
- CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_DISPLAY_NAME_RES_ID_1);
+ CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_NAME_1);
mVirtualCameraController.registerCamera(config);
mVirtualCameraController.unregisterCamera(config);
@@ -109,9 +109,9 @@ public class VirtualCameraControllerTest {
@Test
public void close_unregistersAllCameras() throws Exception {
mVirtualCameraController.registerCamera(createVirtualCameraConfig(
- CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_DISPLAY_NAME_RES_ID_1));
+ CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT, CAMERA_NAME_1));
mVirtualCameraController.registerCamera(createVirtualCameraConfig(
- CAMERA_WIDTH_2, CAMERA_HEIGHT_2, CAMERA_FORMAT, CAMERA_DISPLAY_NAME_RES_ID_2));
+ CAMERA_WIDTH_2, CAMERA_HEIGHT_2, CAMERA_FORMAT, CAMERA_NAME_2));
mVirtualCameraController.close();
@@ -129,10 +129,10 @@ public class VirtualCameraControllerTest {
}
private VirtualCameraConfig createVirtualCameraConfig(
- int width, int height, int format, int displayNameResId) {
+ int width, int height, int format, String displayName) {
return new VirtualCameraConfig.Builder()
.addStreamConfig(width, height, format)
- .setDisplayNameStringRes(displayNameResId)
+ .setName(displayName)
.setVirtualCameraCallback(mCallbackHandler, createNoOpCallback())
.build();
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
index 2868b7e2bd4d..ae3683961d61 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
@@ -68,10 +68,7 @@ import android.os.Bundle;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.INotificationListener;
import android.service.notification.NotificationListenerFilter;
import android.service.notification.NotificationListenerService;
@@ -111,7 +108,7 @@ import java.util.concurrent.CountDownLatch;
public class NotificationListenersTest extends UiServiceTestCase {
@Rule
- public CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+ public SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Mock
private PackageManager mPm;
@@ -696,8 +693,8 @@ public class NotificationListenersTest extends UiServiceTestCase {
}
@Test
- @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testListenerTrusted_withPermission() throws RemoteException {
+ mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
when(mNm.mPackageManager.checkUidPermission(RECEIVE_SENSITIVE_NOTIFICATIONS, mUid1))
.thenReturn(PERMISSION_GRANTED);
ManagedServices.ManagedServiceInfo info = getMockServiceInfo();
@@ -706,8 +703,8 @@ public class NotificationListenersTest extends UiServiceTestCase {
}
@Test
- @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testListenerTrusted_withSystemSignature() {
+ mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
when(mNm.mPackageManagerInternal.isPlatformSigned(mCn1.getPackageName())).thenReturn(true);
ManagedServices.ManagedServiceInfo info = getMockServiceInfo();
mListeners.onServiceAdded(info);
@@ -715,8 +712,8 @@ public class NotificationListenersTest extends UiServiceTestCase {
}
@Test
- @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testListenerTrusted_withCdmAssociation() throws Exception {
+ mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
mNm.mCompanionManager = mock(ICompanionDeviceManager.class);
AssociationInfo assocInfo = mock(AssociationInfo.class);
when(assocInfo.isRevoked()).thenReturn(false);
@@ -731,16 +728,16 @@ public class NotificationListenersTest extends UiServiceTestCase {
}
@Test
- @RequiresFlagsDisabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testListenerTrusted_ifFlagDisabled() {
+ mSetFlagsRule.disableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
ManagedServices.ManagedServiceInfo info = getMockServiceInfo();
mListeners.onServiceAdded(info);
assertTrue(mListeners.isUidTrusted(mUid1));
}
@Test
- @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testRedaction_whenPosted() {
+ mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
ArrayList<ManagedServices.ManagedServiceInfo> infos = new ArrayList<>();
infos.add(getMockServiceInfo());
doReturn(infos).when(mListeners).getServices();
@@ -762,13 +759,11 @@ public class NotificationListenersTest extends UiServiceTestCase {
mListeners.notifyPostedLocked(r, old);
verify(mListeners, atLeast(1)).redactStatusBarNotification(eq(sbn));
verify(mListeners, never()).redactStatusBarNotification(eq(oldSbn));
-
-
}
@Test
- @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testRedaction_whenPosted_oldRemoved() {
+ mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
ArrayList<ManagedServices.ManagedServiceInfo> infos = new ArrayList<>();
infos.add(getMockServiceInfo());
doReturn(infos).when(mListeners).getServices();
@@ -795,8 +790,8 @@ public class NotificationListenersTest extends UiServiceTestCase {
}
@Test
- @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testRedaction_whenRemoved() {
+ mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
doReturn(mock(StatusBarNotification.class))
.when(mListeners).redactStatusBarNotification(any());
ArrayList<ManagedServices.ManagedServiceInfo> infos = new ArrayList<>();
@@ -816,8 +811,8 @@ public class NotificationListenersTest extends UiServiceTestCase {
}
@Test
- @RequiresFlagsDisabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testRedaction_noneIfFlagDisabled() {
+ mSetFlagsRule.disableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
ArrayList<ManagedServices.ManagedServiceInfo> infos = new ArrayList<>();
infos.add(getMockServiceInfo());
doReturn(infos).when(mListeners).getServices();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 884ea31504bc..3ab7496eff84 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -77,7 +77,9 @@ import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+import static android.service.notification.Adjustment.KEY_CONTEXTUAL_ACTIONS;
import static android.service.notification.Adjustment.KEY_IMPORTANCE;
+import static android.service.notification.Adjustment.KEY_TEXT_REPLIES;
import static android.service.notification.Adjustment.KEY_USER_SENTIMENT;
import static android.service.notification.Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS;
import static android.service.notification.Condition.SOURCE_CONTEXT;
@@ -216,9 +218,6 @@ import android.os.UserManager;
import android.os.WorkSource;
import android.permission.PermissionManager;
import android.platform.test.annotations.EnableFlags;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
import android.platform.test.rule.DeniedDevices;
import android.platform.test.rule.DeviceProduct;
@@ -361,9 +360,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Rule
public TestRule compatChangeRule = new PlatformCompatChangeRule();
- @Rule
- public CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
private TestableNotificationManagerService mService;
private INotificationManager mBinderService;
private NotificationManagerInternal mInternalService;
@@ -9262,41 +9258,46 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
@EnableFlags(android.app.Flags.FLAG_MODES_API)
- public void setAutomaticZenRuleState_fromUserMatchesConditionSource_okay() throws Exception {
+ public void setAutomaticZenRuleState_conditionFromUser_mappedToOriginUser() throws Exception {
ZenModeHelper zenModeHelper = setUpMockZenTest();
mService.setCallerIsNormalPackage();
- Condition withSourceContext = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
- SOURCE_CONTEXT);
- mBinderService.setAutomaticZenRuleState("id", withSourceContext, /* fromUser= */ false);
- verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceContext),
- eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyInt());
-
Condition withSourceUser = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
SOURCE_USER_ACTION);
- mBinderService.setAutomaticZenRuleState("id", withSourceUser, /* fromUser= */ true);
+ mBinderService.setAutomaticZenRuleState("id", withSourceUser);
+
verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceUser),
eq(ZenModeConfig.UPDATE_ORIGIN_USER), anyInt());
}
@Test
@EnableFlags(android.app.Flags.FLAG_MODES_API)
- public void setAutomaticZenRuleState_fromUserDoesNotMatchConditionSource_blocked()
+ public void setAutomaticZenRuleState_fromAppWithConditionNotFromUser_mappedToOriginApp()
throws Exception {
ZenModeHelper zenModeHelper = setUpMockZenTest();
mService.setCallerIsNormalPackage();
Condition withSourceContext = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
SOURCE_CONTEXT);
- assertThrows(IllegalArgumentException.class,
- () -> mBinderService.setAutomaticZenRuleState("id", withSourceContext,
- /* fromUser= */ true));
+ mBinderService.setAutomaticZenRuleState("id", withSourceContext);
- Condition withSourceUser = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
- SOURCE_USER_ACTION);
- assertThrows(IllegalArgumentException.class,
- () -> mBinderService.setAutomaticZenRuleState("id", withSourceUser,
- /* fromUser= */ false));
+ verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceContext),
+ eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyInt());
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void setAutomaticZenRuleState_fromSystemWithConditionNotFromUser_mappedToOriginSystem()
+ throws Exception {
+ ZenModeHelper zenModeHelper = setUpMockZenTest();
+ mService.isSystemUid = true;
+
+ Condition withSourceContext = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
+ SOURCE_CONTEXT);
+ mBinderService.setAutomaticZenRuleState("id", withSourceContext);
+
+ verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceContext),
+ eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyInt());
}
private ZenModeHelper setUpMockZenTest() {
@@ -11778,8 +11779,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testGetActiveNotificationsFromListener_redactNotification() throws Exception {
+ mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
NotificationRecord r =
generateNotificationRecord(mTestNotificationChannel, 0, 0);
mService.addNotification(r);
@@ -11808,12 +11809,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- @RequiresFlagsEnabled(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS)
public void testGetSnoozedNotificationsFromListener_redactNotification() throws Exception {
+ mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
NotificationRecord r =
generateNotificationRecord(mTestNotificationChannel, 0, 0);
- mService.addNotification(r);
- mService.snoozeNotificationInt(r.getKey(), 1000, null, mListener);
+ when(mSnoozeHelper.getSnoozed()).thenReturn(List.of(r));
when(mListeners.isUidTrusted(anyInt())).thenReturn(false);
when(mListeners.hasSensitiveContent(any())).thenReturn(true);
StatusBarNotification redacted = generateRedactedSbn(mTestNotificationChannel, 1, 1);
@@ -11993,6 +11993,97 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void testMakeRankingUpdate_redactsIfRecordSensitiveAndServiceUntrusted() {
+ mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
+ when(mListeners.isUidTrusted(anyInt())).thenReturn(false);
+ when(mListeners.hasSensitiveContent(any())).thenReturn(true);
+ NotificationRecord pkgA = new NotificationRecord(mContext,
+ generateSbn("a", 1000, 9, 0), mTestNotificationChannel);
+ addSmartActionsAndReplies(pkgA);
+ mService.addNotification(pkgA);
+ NotificationRecord pkgB = new NotificationRecord(mContext,
+ generateSbn("b", 1001, 9, 0), mTestNotificationChannel);
+ addSmartActionsAndReplies(pkgB);
+ mService.addNotification(pkgB);
+
+ ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class);
+ when(info.enabledAndUserMatches(anyInt())).thenReturn(true);
+ when(info.isSameUser(anyInt())).thenReturn(true);
+ NotificationRankingUpdate nru = mService.makeRankingUpdateLocked(info);
+ NotificationListenerService.Ranking ranking =
+ nru.getRankingMap().getRawRankingObject(pkgA.getSbn().getKey());
+ assertEquals(0, ranking.getSmartActions().size());
+ assertEquals(0, ranking.getSmartReplies().size());
+ NotificationListenerService.Ranking ranking2 =
+ nru.getRankingMap().getRawRankingObject(pkgB.getSbn().getKey());
+ assertEquals(0, ranking2.getSmartActions().size());
+ assertEquals(0, ranking2.getSmartReplies().size());
+ }
+
+ @Test
+ public void testMakeRankingUpdate_doestntRedactIfFlagDisabled() {
+ mSetFlagsRule.disableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
+ when(mListeners.isUidTrusted(anyInt())).thenReturn(false);
+ when(mListeners.hasSensitiveContent(any())).thenReturn(true);
+ NotificationRecord pkgA = new NotificationRecord(mContext,
+ generateSbn("a", 1000, 9, 0), mTestNotificationChannel);
+ addSmartActionsAndReplies(pkgA);
+
+ mService.addNotification(pkgA);
+ ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class);
+ when(info.enabledAndUserMatches(anyInt())).thenReturn(true);
+ when(info.isSameUser(anyInt())).thenReturn(true);
+ NotificationRankingUpdate nru = mService.makeRankingUpdateLocked(info);
+ NotificationListenerService.Ranking ranking =
+ nru.getRankingMap().getRawRankingObject(pkgA.getSbn().getKey());
+ assertEquals(1, ranking.getSmartActions().size());
+ assertEquals(1, ranking.getSmartReplies().size());
+ }
+
+ @Test
+ public void testMakeRankingUpdate_doesntRedactIfNotSensitiveOrServiceTrusted() {
+ mSetFlagsRule.disableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
+ NotificationRecord pkgA = new NotificationRecord(mContext,
+ generateSbn("a", 1000, 9, 0), mTestNotificationChannel);
+ addSmartActionsAndReplies(pkgA);
+
+ mService.addNotification(pkgA);
+ ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class);
+ when(info.enabledAndUserMatches(anyInt())).thenReturn(true);
+ when(info.isSameUser(anyInt())).thenReturn(true);
+
+ // No sensitive content, no redaction
+ when(mListeners.isUidTrusted(eq(1000))).thenReturn(false);
+ when(mListeners.hasSensitiveContent(any())).thenReturn(false);
+ NotificationRankingUpdate nru = mService.makeRankingUpdateLocked(info);
+ NotificationListenerService.Ranking ranking =
+ nru.getRankingMap().getRawRankingObject(pkgA.getSbn().getKey());
+ assertEquals(1, ranking.getSmartActions().size());
+ assertEquals(1, ranking.getSmartReplies().size());
+
+ // trusted listener, no redaction
+ when(mListeners.isUidTrusted(eq(1000))).thenReturn(true);
+ when(mListeners.hasSensitiveContent(any())).thenReturn(true);
+ nru = mService.makeRankingUpdateLocked(info);
+ ranking = nru.getRankingMap().getRawRankingObject(pkgA.getSbn().getKey());
+ assertEquals(1, ranking.getSmartActions().size());
+ assertEquals(1, ranking.getSmartReplies().size());
+ }
+
+ private void addSmartActionsAndReplies(NotificationRecord record) {
+ Bundle b = new Bundle();
+ ArrayList<Notification.Action> actions = new ArrayList<>();
+ actions.add(new Notification.Action(0, "", null));
+ b.putParcelableArrayList(KEY_CONTEXTUAL_ACTIONS, actions);
+ ArrayList<CharSequence> replies = new ArrayList<>(List.of("test"));
+ b.putCharSequenceArrayList(KEY_TEXT_REPLIES, replies);
+ Adjustment a = new Adjustment(record.getSbn().getPackageName(), record.getSbn().getKey(),
+ b, "", record.getUserId());
+ record.addAdjustment(a);
+ record.applyAdjustments();
+ }
+
+ @Test
public void testMaybeShowReviewPermissionsNotification_flagOff() {
mService.setShowReviewPermissionsNotification(false);
reset(mMockNm);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 718c59875c1a..e88a00b93b03 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -161,6 +161,7 @@ import androidx.test.filters.MediumTest;
import com.android.internal.R;
import com.android.server.wm.ActivityRecord.State;
+import com.android.window.flags.Flags;
import org.junit.Assert;
import org.junit.Before;
@@ -320,7 +321,7 @@ public class ActivityRecordTests extends WindowTestsBase {
}
private void ensureActivityConfiguration(ActivityRecord activity) {
- activity.ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */);
+ activity.ensureActivityConfiguration();
}
@Test
@@ -718,7 +719,7 @@ public class ActivityRecordTests extends WindowTestsBase {
// Clear size compat.
activity.clearSizeCompatMode();
- activity.ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */);
+ activity.ensureActivityConfiguration();
mDisplayContent.sendNewConfiguration();
// Relaunching the app should still respect the orientation request.
@@ -819,8 +820,7 @@ public class ActivityRecordTests extends WindowTestsBase {
task.onConfigurationChanged(newConfig);
- activity.ensureActivityConfiguration(0 /* globalChanges */,
- false /* preserveWindow */, true /* ignoreVisibility */);
+ activity.ensureActivityConfiguration(true /* ignoreVisibility */);
final ActivityConfigurationChangeItem expected =
ActivityConfigurationChangeItem.obtain(activity.token,
@@ -1563,8 +1563,7 @@ public class ActivityRecordTests extends WindowTestsBase {
topActivity.nowVisible = true;
topActivity.setState(RESUMED, "true");
doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible(
- any() /* starting */, anyInt() /* configChanges */,
- anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */);
+ any() /* starting */, anyBoolean() /* notifyClients */);
topActivity.setShowWhenLocked(true);
// Verify the stack-top activity is occluded keyguard.
@@ -1624,7 +1623,6 @@ public class ActivityRecordTests extends WindowTestsBase {
secondActivity.finishing = true;
secondActivity.completeFinishing("test");
verify(secondActivity.mDisplayContent).ensureActivitiesVisible(null /* starting */,
- 0 /* configChanges */ , false /* preserveWindows */,
true /* notifyClients */);
// Finish the first activity
@@ -1632,7 +1630,6 @@ public class ActivityRecordTests extends WindowTestsBase {
firstActivity.setVisibleRequested(true);
firstActivity.completeFinishing("test");
verify(firstActivity.mDisplayContent, times(2)).ensureActivitiesVisible(null /* starting */,
- 0 /* configChanges */ , false /* preserveWindows */,
true /* notifyClients */);
// Remove the translucent activity and clear invocations for next test
@@ -1960,6 +1957,7 @@ public class ActivityRecordTests extends WindowTestsBase {
display.continueUpdateOrientationForDiffOrienLaunchingApp();
assertTrue(display.isFixedRotationLaunchingApp(activity));
+ activity.stopFreezingScreen(true /* unfreezeSurfaceNow */, true /* force */);
// Simulate the rotation has been updated to previous one, e.g. sensor updates before the
// remote rotation is completed.
doReturn(originalRotation).when(displayRotation).rotationForOrientation(
@@ -1970,14 +1968,12 @@ public class ActivityRecordTests extends WindowTestsBase {
activity.finishFixedRotationTransform();
final ScreenRotationAnimation rotationAnim = display.getRotationAnimation();
assertNotNull(rotationAnim);
- rotationAnim.setRotation(display.getPendingTransaction(), originalRotation);
// Because the display doesn't rotate, the rotated activity needs to cancel the fixed
// rotation. There should be a rotation animation to cover the change of activity.
verify(activity).onCancelFixedRotationTransform(rotatedInfo.rotation);
assertTrue(activity.isFreezingScreen());
assertFalse(displayRotation.isRotatingSeamlessly());
- assertTrue(rotationAnim.isRotating());
// Simulate the remote rotation has completed and the configuration doesn't change, then
// the rotated activity should also be restored by clearing the transform.
@@ -3370,7 +3366,7 @@ public class ActivityRecordTests extends WindowTestsBase {
// to client if the app didn't request IME visible.
assertFalse(app2.mActivityRecord.mImeInsetsFrozenUntilStartInput);
- if (mWm.mFlags.mWindowStateResizeItemFlag) {
+ if (Flags.bundleClientTransactionFlag()) {
verify(app2.getProcess()).scheduleClientTransactionItem(
isA(WindowStateResizeItem.class));
} else {
@@ -3697,7 +3693,7 @@ public class ActivityRecordTests extends WindowTestsBase {
doReturn(false).when(activity).showToCurrentUser();
spyOn(taskFragment);
doReturn(false).when(taskFragment).shouldBeVisible(any());
- display.ensureActivitiesVisible(null, 0, false, false);
+ display.ensureActivitiesVisible(null /* starting */, false /* notifyClients */);
assertFalse(activity.isVisibleRequested());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
index c757457a55ec..09f677e701df 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
@@ -172,7 +172,7 @@ public class ClientLifecycleManagerTests {
@Test
public void testScheduleTransactionItemUnlocked() throws RemoteException {
// Use non binder client to get non-recycled ClientTransaction.
- mLifecycleManager.scheduleTransactionItemUnlocked(mNonBinderClient, mTransactionItem);
+ mLifecycleManager.scheduleTransactionItemNow(mNonBinderClient, mTransactionItem);
// Dispatch immediately.
assertTrue(mLifecycleManager.mPendingTransactions.isEmpty());
@@ -217,6 +217,8 @@ public class ClientLifecycleManagerTests {
@Test
public void testDispatchPendingTransactions() throws RemoteException {
+ mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+
mLifecycleManager.mPendingTransactions.put(mClientBinder, mTransaction);
mLifecycleManager.dispatchPendingTransactions();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index dfe79bf1e3e6..6497ee9cb1f2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -75,7 +75,6 @@ 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.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.same;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
@@ -490,7 +489,7 @@ public class DisplayContentTests extends WindowTestsBase {
newOverrideConfig.fontScale += 0.3;
defaultDisplay.updateDisplayOverrideConfigurationLocked(newOverrideConfig,
- null /* starting */, false /* deferResume */, null /* result */);
+ null /* starting */, false /* deferResume */);
// Check that global configuration is updated, as we've updated default display's config.
Configuration globalConfig = mWm.mRoot.getConfiguration();
@@ -499,7 +498,7 @@ public class DisplayContentTests extends WindowTestsBase {
// Return back to original values.
defaultDisplay.updateDisplayOverrideConfigurationLocked(currentConfig,
- null /* starting */, false /* deferResume */, null /* result */);
+ null /* starting */, false /* deferResume */);
globalConfig = mWm.mRoot.getConfiguration();
assertEquals(currentConfig.densityDpi, globalConfig.densityDpi);
assertEquals(currentConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */);
@@ -1176,7 +1175,7 @@ public class DisplayContentTests extends WindowTestsBase {
activity.setRequestedOrientation(newOrientation);
verify(dc, never()).updateDisplayOverrideConfigurationLocked(any(), eq(activity),
- anyBoolean(), same(null));
+ anyBoolean());
assertEquals(ROTATION_180, dc.getRotation());
}
@@ -2123,10 +2122,8 @@ public class DisplayContentTests extends WindowTestsBase {
// Once transition starts, rotation is applied and transition shows DC rotating.
testPlayer.startTransition();
waitUntilHandlersIdle();
- verify(activity1).ensureActivityConfiguration(anyInt(), anyBoolean(), anyBoolean(),
- anyBoolean());
- verify(activity2).ensureActivityConfiguration(anyInt(), anyBoolean(), anyBoolean(),
- anyBoolean());
+ verify(activity1).ensureActivityConfiguration(anyBoolean(), anyBoolean());
+ verify(activity2).ensureActivityConfiguration(anyBoolean(), anyBoolean());
assertNotEquals(origRot, dc.getConfiguration().windowConfiguration.getRotation());
assertNotNull(testPlayer.mLastReady);
assertTrue(testPlayer.mController.isPlaying());
@@ -2248,11 +2245,11 @@ public class DisplayContentTests extends WindowTestsBase {
// The assertion will fail if DisplayArea#ensureActivitiesVisible is called twice.
assertFalse(called[0]);
called[0] = true;
- mDisplayContent.ensureActivitiesVisible(null, 0, false, false);
+ mDisplayContent.ensureActivitiesVisible(null, false);
return null;
- }).when(mockTda).ensureActivitiesVisible(any(), anyInt(), anyBoolean(), anyBoolean());
+ }).when(mockTda).ensureActivitiesVisible(any(), anyBoolean());
- mDisplayContent.ensureActivitiesVisible(null, 0, false, false);
+ mDisplayContent.ensureActivitiesVisible(null, false);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index 8de45b039f62..32b3558ba397 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -106,8 +106,7 @@ public class RecentsAnimationTest extends WindowTestsBase {
topActivity.getRootTask().moveToFront("testRecentsActivityVisiblility");
doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible(
- any() /* starting */, anyInt() /* configChanges */,
- anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */);
+ any() /* starting */, anyBoolean() /* notifyClients */);
RecentsAnimationCallbacks recentsAnimation = startRecentsActivity(
mRecentsComponent, true /* getRecentsAnimation */);
@@ -178,8 +177,7 @@ public class RecentsAnimationTest extends WindowTestsBase {
mAtm.startRecentsActivity(recentsIntent, 0 /* eventTime */,
null /* recentsAnimationRunner */);
- verify(recentsActivity).ensureActivityConfiguration(anyInt() /* globalChanges */,
- anyBoolean() /* preserveWindow */, eq(true) /* ignoreVisibility */);
+ verify(recentsActivity).ensureActivityConfiguration(eq(true) /* ignoreVisibility */);
assertThat(mSupervisor.mStoppingActivities).contains(recentsActivity);
}
@@ -199,8 +197,7 @@ public class RecentsAnimationTest extends WindowTestsBase {
"testRestartRecentsActivity");
doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible(
- any() /* starting */, anyInt() /* configChanges */,
- anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */);
+ any() /* starting */, anyBoolean() /* notifyClients */);
doReturn(app).when(mAtm).getProcessController(eq(recentActivity.processName), anyInt());
doNothing().when(mClientLifecycleManager).scheduleTransaction(any());
@@ -354,8 +351,7 @@ public class RecentsAnimationTest extends WindowTestsBase {
doReturn(TEST_USER_ID).when(mAtm).getCurrentUserId();
doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible(
- any() /* starting */, anyInt() /* configChanges */,
- anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */);
+ any() /* starting */, anyBoolean() /* notifyClients */);
startRecentsActivity(otherUserHomeActivity.getTask().getBaseIntent().getComponent(),
true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index 89cd7266915c..b5883b1aefe5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -823,8 +823,7 @@ public class RootTaskTests extends WindowTestsBase {
.build();
doReturn(false).when(secondActivity).occludesParent();
- homeRootTask.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
- false /* preserveWindows */);
+ homeRootTask.ensureActivitiesVisible(null /* starting */);
assertTrue(firstActivity.shouldBeVisible());
}
@@ -1419,8 +1418,7 @@ public class RootTaskTests extends WindowTestsBase {
// Any common path that updates activity visibility should clear the unknown visibility
// records that are no longer visible according to hierarchy.
- task.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
- false /* preserveWindows */);
+ task.ensureActivitiesVisible(null /* starting */);
// Assume the top activity relayouted, just remove it directly.
unknownAppVisibilityController.appRemovedOrHidden(activities[1]);
// All unresolved records should be removed.
@@ -1441,8 +1439,7 @@ public class RootTaskTests extends WindowTestsBase {
doNothing().when(mSupervisor).startSpecificActivity(any(), anyBoolean(),
anyBoolean());
- task.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
- false /* preserveWindows */);
+ task.ensureActivitiesVisible(null /* starting */);
verify(mSupervisor).startSpecificActivity(any(), eq(false) /* andResume */,
anyBoolean());
}
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 c3102e08489d..5518c604446d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -947,7 +947,7 @@ public class SizeCompatTests extends WindowTestsBase {
// Recompute the natural configuration in the new display.
mActivity.clearSizeCompatMode();
- mActivity.ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */);
+ mActivity.ensureActivityConfiguration();
// Because the display cannot rotate, the portrait activity will fit the short side of
// display with keeping portrait bounds [200, 0 - 700, 1000] in center.
assertEquals(newDisplayBounds.height(), currentBounds.height());
@@ -4858,7 +4858,7 @@ public class SizeCompatTests extends WindowTestsBase {
}
// Make sure to use the provided configuration to construct the size compat fields.
activity.clearSizeCompatMode();
- activity.ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */);
+ activity.ensureActivityConfiguration();
// Make sure the display configuration reflects the change of activity.
if (activity.mDisplayContent.updateOrientation()) {
activity.mDisplayContent.sendNewConfiguration();
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 8cd9ff359111..90493d4dc95f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -375,8 +375,7 @@ public class SystemServicesTestRule implements TestRule {
// Always keep things awake.
doReturn(true).when(mWmService.mRoot).hasAwakeDisplay();
// Called when moving activity to pinned stack.
- doNothing().when(mWmService.mRoot).ensureActivitiesVisible(any(),
- anyInt(), anyBoolean(), anyBoolean());
+ doNothing().when(mWmService.mRoot).ensureActivitiesVisible(any(), anyBoolean());
spyOn(mWmService.mDisplayWindowSettings);
spyOn(mWmService.mDisplayWindowSettingsProvider);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index ec068bed1a3b..00e22fd7142a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -269,8 +269,7 @@ public class TaskFragmentTest extends WindowTestsBase {
mTaskFragment.getTask().addChild(activityBelow, 0);
// Ensure the activity below is visible
- mTaskFragment.getTask().ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
- false /* preserveWindows */);
+ mTaskFragment.getTask().ensureActivitiesVisible(null /* starting */);
assertEquals(true, activityBelow.isVisibleRequested());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index da7612b17dc9..45e1e9579f3b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -339,16 +339,14 @@ public class TaskTests extends WindowTestsBase {
// Check visibility of occluded tasks
doReturn(false).when(leafTask1).shouldBeVisible(any());
doReturn(true).when(leafTask2).shouldBeVisible(any());
- rootTask.ensureActivitiesVisible(
- null /* starting */ , 0 /* configChanges */, false /* preserveWindows */);
+ rootTask.ensureActivitiesVisible(null /* starting */);
assertFalse(activity1.isVisible());
assertTrue(activity2.isVisible());
// Check visibility of not occluded tasks
doReturn(true).when(leafTask1).shouldBeVisible(any());
doReturn(true).when(leafTask2).shouldBeVisible(any());
- rootTask.ensureActivitiesVisible(
- null /* starting */ , 0 /* configChanges */, false /* preserveWindows */);
+ rootTask.ensureActivitiesVisible(null /* starting */);
assertTrue(activity1.isVisible());
assertTrue(activity2.isVisible());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index bd111ada8550..52e2d8abc2d5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -89,8 +89,8 @@ class TestWindowManagerPolicy implements WindowManagerPolicy {
}
@Override
- public int interceptMotionBeforeQueueingNonInteractive(int displayId, long whenNanos,
- int policyFlags) {
+ public int interceptMotionBeforeQueueingNonInteractive(int displayId, int source, int action,
+ long whenNanos, int policyFlags) {
return 0;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 71447e72de8c..fddd77176bd9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1495,8 +1495,7 @@ public class TransitionTests extends WindowTestsBase {
verify(taskSnapshotController, times(0)).recordSnapshot(eq(task1));
enteringAnimReports.clear();
- doCallRealMethod().when(mWm.mRoot).ensureActivitiesVisible(any(),
- anyInt(), anyBoolean(), anyBoolean());
+ doCallRealMethod().when(mWm.mRoot).ensureActivitiesVisible(any(), anyBoolean());
final boolean[] wasInFinishingTransition = { false };
controller.registerLegacyListener(new WindowManagerInternal.AppTransitionListener() {
@Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index e31ee1174656..400e4b6686cb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -38,7 +38,6 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
@@ -306,10 +305,12 @@ public class WindowProcessControllerTests extends WindowTestsBase {
@Test
public void testCachedStateConfigurationChange() throws RemoteException {
- doNothing().when(mClientLifecycleManager).scheduleTransactionItemUnlocked(any(), any());
+ doNothing().when(mClientLifecycleManager).scheduleTransactionItemNow(any(), any());
final IApplicationThread thread = mWpc.getThread();
final Configuration newConfig = new Configuration(mWpc.getConfiguration());
newConfig.densityDpi += 100;
+ mWpc.mWindowSession = getTestSession();
+ mWpc.mWindowSession.onWindowAdded(mock(WindowState.class));
// Non-cached state will send the change directly.
mWpc.setReportedProcState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
clearInvocations(mClientLifecycleManager);
@@ -322,13 +323,13 @@ public class WindowProcessControllerTests extends WindowTestsBase {
newConfig.densityDpi += 100;
mWpc.onConfigurationChanged(newConfig);
verify(mClientLifecycleManager, never()).scheduleTransactionItem(eq(thread), any());
- verify(mClientLifecycleManager, never()).scheduleTransactionItemUnlocked(eq(thread), any());
+ verify(mClientLifecycleManager, never()).scheduleTransactionItemNow(eq(thread), any());
// Cached -> non-cached will send the previous deferred config immediately.
mWpc.setReportedProcState(ActivityManager.PROCESS_STATE_RECEIVER);
final ArgumentCaptor<ConfigurationChangeItem> captor =
ArgumentCaptor.forClass(ConfigurationChangeItem.class);
- verify(mClientLifecycleManager).scheduleTransactionItemUnlocked(
+ verify(mClientLifecycleManager).scheduleTransactionItemNow(
eq(thread), captor.capture());
final ClientTransactionHandler client = mock(ClientTransactionHandler.class);
captor.getValue().preExecute(client);
@@ -432,7 +433,7 @@ public class WindowProcessControllerTests extends WindowTestsBase {
mWpc.updateAppSpecificSettingsForAllActivitiesInPackage(DEFAULT_COMPONENT_PACKAGE_NAME,
Configuration.UI_MODE_NIGHT_YES, LocaleList.forLanguageTags("en-XA"),
GRAMMATICAL_GENDER_NOT_SPECIFIED);
- verify(activity).ensureActivityConfiguration(anyInt(), anyBoolean());
+ verify(activity).ensureActivityConfiguration();
}
@Test
@@ -443,7 +444,7 @@ public class WindowProcessControllerTests extends WindowTestsBase {
Configuration.UI_MODE_NIGHT_YES, LocaleList.forLanguageTags("en-XA"),
GRAMMATICAL_GENDER_NOT_SPECIFIED);
verify(activity, never()).applyAppSpecificConfig(anyInt(), any(), anyInt());
- verify(activity, never()).ensureActivityConfiguration(anyInt(), anyBoolean());
+ verify(activity, never()).ensureActivityConfiguration();
}
@Test
diff --git a/services/usb/Android.bp b/services/usb/Android.bp
index 1dc5dcf320e0..3a0a6abb2307 100644
--- a/services/usb/Android.bp
+++ b/services/usb/Android.bp
@@ -35,4 +35,7 @@ java_library_static {
"android.hardware.usb-V1.3-java",
"android.hardware.usb-V3-java",
],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 22a5cd7a478a..9e292be7f767 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -2115,6 +2115,20 @@ public class TelephonyManager {
public static final String ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE =
"com.android.omadm.service.CONFIGURATION_UPDATE";
+ /**
+ * Activity action: Show setting to reset mobile networks.
+ *
+ * <p>On devices with a settings activity to reset mobile networks, the activity should be
+ * launched without additional permissions.
+ *
+ * <p>On some devices, this settings activity may not exist. Callers should ensure that this
+ * case is appropriately handled.
+ */
+ @FlaggedApi(Flags.FLAG_RESET_MOBILE_NETWORK_SETTINGS)
+ @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_RESET_MOBILE_NETWORK_SETTINGS =
+ "android.telephony.action.RESET_MOBILE_NETWORK_SETTINGS";
+
//
//
// Device Info
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
index 25d208db14ec..5cbb1aad1851 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
@@ -77,7 +77,9 @@ data class EnforcePermissionFix(
.autoFix()
.build()
- return LintFix.create().composite(annotateFix, *replaceOrRemoveFixes.toTypedArray())
+ return LintFix.create()
+ .name(annotateFix.getDisplayName())
+ .composite(annotateFix, *replaceOrRemoveFixes.toTypedArray())
}
private val annotation: String