summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apct-tests/perftests/contentcapture/AndroidManifest.xml2
-rw-r--r--apct-tests/perftests/contentcapture/src/android/view/contentcapture/AbstractContentCapturePerfTestCase.java51
-rw-r--r--apct-tests/perftests/contentcapture/src/android/view/contentcapture/CustomTestActivity.java34
-rw-r--r--apct-tests/perftests/contentcapture/src/android/view/contentcapture/LoginTest.java28
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobInfo.java116
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java101
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java99
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java108
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java69
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java37
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java8
-rw-r--r--core/api/current.txt30
-rw-r--r--core/api/system-current.txt22
-rw-r--r--core/api/test-current.txt1
-rw-r--r--core/java/android/app/Activity.java56
-rw-r--r--core/java/android/app/ActivityOptions.java53
-rw-r--r--core/java/android/app/ApplicationPackageManager.java11
-rw-r--r--core/java/android/app/IActivityTaskManager.aidl5
-rw-r--r--core/java/android/app/StatusBarManager.java16
-rw-r--r--core/java/android/companion/virtual/IVirtualDevice.aidl5
-rw-r--r--core/java/android/companion/virtual/VirtualDeviceManager.java36
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl2
-rw-r--r--core/java/android/content/pm/PackageManager.java24
-rw-r--r--core/java/android/hardware/display/DisplayManagerInternal.java7
-rw-r--r--core/java/android/hardware/input/VirtualNavigationTouchpad.java60
-rw-r--r--core/java/android/hardware/input/VirtualNavigationTouchpadConfig.aidl19
-rw-r--r--core/java/android/hardware/input/VirtualNavigationTouchpadConfig.java115
-rw-r--r--core/java/android/net/vcn/VcnGatewayConnectionConfig.java113
-rw-r--r--core/java/android/os/Looper.java11
-rw-r--r--core/java/android/provider/Settings.java43
-rw-r--r--core/java/android/service/voice/AbstractHotwordDetector.java21
-rw-r--r--core/java/android/service/voice/AlwaysOnHotwordDetector.java20
-rw-r--r--core/java/android/service/voice/HotwordDetectionService.java2
-rw-r--r--core/java/android/service/voice/HotwordDetector.java2
-rw-r--r--core/java/android/service/voice/ISandboxedDetectionService.aidl (renamed from core/java/android/service/voice/IHotwordDetectionService.aidl)4
-rw-r--r--core/java/android/service/voice/SoftwareHotwordDetector.java4
-rw-r--r--core/java/android/service/voice/VoiceInteractionService.java6
-rw-r--r--core/java/android/service/voice/VoiceInteractionServiceInfo.java8
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java2
-rw-r--r--core/java/android/util/FeatureFlagUtils.java1
-rw-r--r--core/java/android/view/AttachedSurfaceControl.java4
-rw-r--r--core/java/android/view/IWindow.aidl2
-rw-r--r--core/java/android/view/IWindowManager.aidl6
-rw-r--r--core/java/android/view/MotionEvent.java1
-rw-r--r--core/java/android/view/SurfaceControlViewHost.java16
-rw-r--r--core/java/android/view/SurfaceView.java47
-rw-r--r--core/java/android/view/ViewRootImpl.java189
-rw-r--r--core/java/android/view/WindowCallbacks.java18
-rw-r--r--core/java/android/view/WindowlessWindowManager.java4
-rw-r--r--core/java/android/view/accessibility/AccessibilityManager.java119
-rw-r--r--core/java/android/view/accessibility/IAccessibilityManager.aidl2
-rw-r--r--core/java/android/view/accessibility/IAccessibilityManagerClient.aidl2
-rw-r--r--core/java/android/window/SurfaceSyncGroup.java274
-rw-r--r--core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl14
-rw-r--r--core/java/com/android/internal/app/IVoiceInteractionSessionListener.aidl2
-rw-r--r--core/java/com/android/internal/policy/DecorView.java13
-rw-r--r--core/java/com/android/internal/view/BaseIWindow.java2
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java12
-rw-r--r--core/jni/android_media_AudioFormat.h5
-rw-r--r--core/jni/android_media_AudioProfile.h4
-rw-r--r--core/proto/android/providers/settings/secure.proto11
-rw-r--r--core/proto/android/server/background_install_control.proto (renamed from services/core/jni/onload_settings.cpp)31
-rw-r--r--core/res/AndroidManifest.xml9
-rw-r--r--core/res/OWNERS5
-rw-r--r--core/res/res/values/attrs.xml4
-rw-r--r--core/res/res/values/config.xml4
-rw-r--r--core/res/res/values/locale_config.xml96
-rw-r--r--core/res/res/values/public-staging.xml1
-rw-r--r--core/res/res/values/symbols.xml3
-rw-r--r--core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java43
-rw-r--r--data/etc/services.core.protolog.json12
-rw-r--r--identity/java/android/security/identity/AuthenticationKeyMetadata.java6
-rw-r--r--libs/WindowManager/Shell/AndroidManifest.xml1
-rw-r--r--libs/WindowManager/Shell/res/layout/tv_pip_menu.xml70
-rw-r--r--libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml1
-rw-r--r--libs/WindowManager/Shell/res/values-tvdpi/dimen.xml2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java55
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java87
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java245
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java193
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java96
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java149
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java499
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java293
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java83
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java47
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java42
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java309
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java209
-rw-r--r--media/java/android/media/AudioFormat.java10
-rw-r--r--media/java/android/media/AudioProfile.java7
-rw-r--r--media/java/android/media/audio/common/AidlConversion.java4
-rw-r--r--media/java/android/media/projection/IMediaProjectionCallback.aidl1
-rw-r--r--media/java/android/media/projection/IMediaProjectionManager.aidl20
-rw-r--r--media/java/android/media/projection/MediaProjection.java53
-rw-r--r--media/java/android/media/projection/MediaProjectionConfig.aidl19
-rw-r--r--media/java/android/media/projection/MediaProjectionConfig.java293
-rw-r--r--media/java/android/media/projection/MediaProjectionManager.java60
-rw-r--r--media/tests/projection/Android.bp46
-rw-r--r--media/tests/projection/AndroidManifest.xml32
-rw-r--r--media/tests/projection/AndroidTest.xml32
-rw-r--r--media/tests/projection/TEST_MAPPING13
-rw-r--r--media/tests/projection/src/android/media/projection/MediaProjectionConfigTest.java84
-rw-r--r--media/tests/projection/src/android/media/projection/MediaProjectionManagerTest.java125
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java5
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java5
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordancePreviewConstants.kt24
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml1
-rw-r--r--packages/SystemUI/res/drawable/controls_panel_background.xml22
-rw-r--r--packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml58
-rw-r--r--packages/SystemUI/res/layout/controls_with_favorites.xml2
-rw-r--r--packages/SystemUI/res/values-h411dp/dimens.xml19
-rw-r--r--packages/SystemUI/res/values-h700dp/dimens.xml3
-rw-r--r--packages/SystemUI/res/values-h841dp/dimens.xml19
-rw-r--r--packages/SystemUI/res/values/config.xml12
-rw-r--r--packages/SystemUI/res/values/dimens.xml5
-rw-r--r--packages/SystemUI/res/values/strings.xml6
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java29
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java11
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java5
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java5
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java25
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java6
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java5
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt115
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java45
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancePosition.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt142
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt207
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRendererFactory.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt138
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt89
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverUiEventLogger.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryDumper.kt173
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt194
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeter.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt139
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt84
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java44
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/stylus/OWNERS8
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt395
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt71
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java25
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt159
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt136
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt10
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java93
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt41
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt76
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt45
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt297
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt664
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt76
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java42
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt127
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt47
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt113
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt659
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt3
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java51
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java22
-rw-r--r--services/companion/java/com/android/server/companion/virtual/InputController.java34
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java48
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java94
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java7
-rw-r--r--services/core/java/com/android/server/am/UserController.java6
-rw-r--r--services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java19
-rw-r--r--services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java11
-rw-r--r--services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java279
-rw-r--r--services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java8
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java4637
-rw-r--r--services/core/java/com/android/server/appop/AppOpsServiceImpl.java4742
-rw-r--r--services/core/java/com/android/server/appop/AppOpsServiceInterface.java494
-rw-r--r--services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java6
-rw-r--r--services/core/java/com/android/server/appop/AttributedOp.java78
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java6
-rw-r--r--services/core/java/com/android/server/audio/AudioServiceEvents.java49
-rw-r--r--services/core/java/com/android/server/audio/SoundDoseHelper.java12
-rw-r--r--services/core/java/com/android/server/display/ColorFade.java2
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java57
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java33
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController2.java79
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplay.java1
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplayMapper.java83
-rw-r--r--services/core/java/com/android/server/display/VirtualDisplayAdapter.java9
-rw-r--r--services/core/java/com/android/server/display/state/DisplayStateController.java108
-rw-r--r--services/core/java/com/android/server/input/InputManagerInternal.java15
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java53
-rw-r--r--services/core/java/com/android/server/input/NativeInputManagerService.java5
-rw-r--r--services/core/java/com/android/server/locksettings/LockSettingsService.java70
-rw-r--r--services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java54
-rw-r--r--services/core/java/com/android/server/pm/BackgroundInstallControlService.java476
-rw-r--r--services/core/java/com/android/server/pm/Computer.java5
-rw-r--r--services/core/java/com/android/server/pm/ComputerEngine.java44
-rw-r--r--services/core/java/com/android/server/pm/DexOptHelper.java19
-rw-r--r--services/core/java/com/android/server/pm/IPackageManagerBase.java7
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java96
-rw-r--r--services/core/java/com/android/server/pm/Settings.java33
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java4
-rw-r--r--services/core/java/com/android/server/pm/UserVisibilityMediator.java37
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java4
-rw-r--r--services/core/java/com/android/server/power/PowerGroup.java12
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java22
-rw-r--r--services/core/java/com/android/server/trust/TrustManagerService.java4
-rw-r--r--services/core/java/com/android/server/vcn/VcnGatewayConnection.java93
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java7
-rw-r--r--services/core/java/com/android/server/wm/ActivityClientController.java25
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java74
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java13
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java12
-rw-r--r--services/core/java/com/android/server/wm/ContentRecorder.java66
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java6
-rw-r--r--services/core/java/com/android/server/wm/DockedTaskDividerController.java63
-rw-r--r--services/core/java/com/android/server/wm/DragResizeMode.java47
-rw-r--r--services/core/java/com/android/server/wm/LetterboxConfiguration.java47
-rw-r--r--services/core/java/com/android/server/wm/LetterboxUiController.java193
-rw-r--r--services/core/java/com/android/server/wm/ScreenRotationAnimation.java5
-rw-r--r--services/core/java/com/android/server/wm/Task.java37
-rw-r--r--services/core/java/com/android/server/wm/TaskPositioner.java5
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java45
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java14
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerShellCommand.java45
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java3
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java44
-rw-r--r--services/core/java/com/android/server/wm/utils/CoordinateTransforms.java35
-rw-r--r--services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java55
-rw-r--r--services/core/jni/Android.bp25
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp53
-rw-r--r--services/core/jni/com_android_server_pm_Settings.cpp161
-rw-r--r--services/core/jni/onload.cpp2
-rw-r--r--services/java/com/android/server/SystemServer.java9
-rw-r--r--services/permission/java/com/android/server/permission/access/AccessCheckingService.kt2
-rw-r--r--services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt466
-rw-r--r--services/permission/java/com/android/server/permission/access/permission/PermissionService.kt14
-rw-r--r--services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt19
-rw-r--r--services/proguard.flags7
-rw-r--r--services/tests/PackageManagerServiceTests/server/Android.bp1
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java4
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java71
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java36
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/display/state/DisplayStateControllerTest.java156
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java45
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java19
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java6
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java68
-rw-r--r--services/tests/servicestests/src/com/android/server/am/UserControllerTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java35
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java135
-rw-r--r--services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java217
-rw-r--r--services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt24
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java206
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java9
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java91
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/WeakEscrowTokenTests.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java840
-rw-r--r--services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java18
-rw-r--r--services/tests/wmtests/AndroidManifest.xml1
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java32
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java108
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java237
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TestIWindow.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java5
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/utils/CoordinateTransformsTest.java39
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/utils/RotationAnimationUtilsTest.java54
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java8
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java232
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectorSession.java33
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java16
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java26
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java49
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java9
-rw-r--r--telephony/java/android/telephony/SubscriptionManager.java369
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java5
-rw-r--r--telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl1
-rw-r--r--telephony/java/android/telephony/ims/feature/MmTelFeature.java71
-rw-r--r--telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java6
-rw-r--r--telephony/java/com/android/internal/telephony/ISub.aidl2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt132
-rw-r--r--tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java88
-rw-r--r--tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java58
-rw-r--r--tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java66
-rw-r--r--tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java9
341 files changed, 17799 insertions, 10300 deletions
diff --git a/apct-tests/perftests/contentcapture/AndroidManifest.xml b/apct-tests/perftests/contentcapture/AndroidManifest.xml
index 80957c78abf3..6b566afe284d 100644
--- a/apct-tests/perftests/contentcapture/AndroidManifest.xml
+++ b/apct-tests/perftests/contentcapture/AndroidManifest.xml
@@ -18,6 +18,8 @@
<application>
<uses-library android:name="android.test.runner" />
+ <activity android:name="android.perftests.utils.PerfTestActivity"
+ android:exported="true" />
<activity android:name="android.view.contentcapture.CustomTestActivity"
android:exported="true">
</activity>
diff --git a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/AbstractContentCapturePerfTestCase.java b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/AbstractContentCapturePerfTestCase.java
index 9b853fed81d3..0ea2dafbb047 100644
--- a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/AbstractContentCapturePerfTestCase.java
+++ b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/AbstractContentCapturePerfTestCase.java
@@ -22,18 +22,20 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat
import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+import android.app.Activity;
import android.app.Application;
+import android.app.Instrumentation;
import android.content.ContentCaptureOptions;
import android.content.Context;
import android.content.Intent;
import android.os.BatteryManager;
import android.os.UserHandle;
import android.perftests.utils.PerfStatusReporter;
+import android.perftests.utils.PerfTestActivity;
import android.provider.Settings;
import android.util.Log;
import androidx.annotation.NonNull;
-import androidx.test.rule.ActivityTestRule;
import com.android.compatibility.common.util.ActivitiesWatcher;
import com.android.compatibility.common.util.ActivitiesWatcher.ActivityWatcher;
@@ -53,18 +55,18 @@ import org.junit.runners.model.Statement;
public abstract class AbstractContentCapturePerfTestCase {
private static final String TAG = AbstractContentCapturePerfTestCase.class.getSimpleName();
- private static final long GENERIC_TIMEOUT_MS = 10_000;
+ protected static final long GENERIC_TIMEOUT_MS = 5_000;
private static int sOriginalStayOnWhilePluggedIn;
- private static Context sContext = getInstrumentation().getTargetContext();
+ protected static final Instrumentation sInstrumentation = getInstrumentation();
+ protected static final Context sContext = sInstrumentation.getTargetContext();
protected ActivitiesWatcher mActivitiesWatcher;
- private MyContentCaptureService.ServiceWatcher mServiceWatcher;
+ /** A simple activity as the task root to reduce the noise of pause and animation time. */
+ protected Activity mEntryActivity;
- @Rule
- public ActivityTestRule<CustomTestActivity> mActivityRule =
- new ActivityTestRule<>(CustomTestActivity.class, false, false);
+ private MyContentCaptureService.ServiceWatcher mServiceWatcher;
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@@ -220,6 +222,17 @@ public abstract class AbstractContentCapturePerfTestCase {
}
}
+ @Before
+ public void setUp() {
+ mEntryActivity = sInstrumentation.startActivitySync(
+ PerfTestActivity.createLaunchIntent(sInstrumentation.getContext()));
+ }
+
+ @After
+ public void tearDown() {
+ mEntryActivity.finishAndRemoveTask();
+ }
+
/**
* Sets {@link MyContentCaptureService} as the service for the current user and waits until
* its created, then add the perf test package into allow list.
@@ -248,20 +261,24 @@ public abstract class AbstractContentCapturePerfTestCase {
}
/**
- * Launch test activity with give layout and parameter
+ * Returns the intent which will launch CustomTestActivity.
*/
- protected CustomTestActivity launchActivity(int layoutId, int numViews) {
- final Intent intent = new Intent(sContext, CustomTestActivity.class);
+ protected Intent getLaunchIntent(int layoutId, int numViews) {
+ final Intent intent = new Intent(sContext, CustomTestActivity.class)
+ // Use NEW_TASK because the context is not activity. It is still in the same task
+ // of PerfTestActivity because of the same task affinity. Use NO_ANIMATION because
+ // this test focuses on launch time instead of animation duration.
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION);
intent.putExtra(INTENT_EXTRA_LAYOUT_ID, layoutId);
intent.putExtra(INTENT_EXTRA_CUSTOM_VIEWS, numViews);
- return mActivityRule.launchActivity(intent);
+ return intent;
}
- protected void finishActivity() {
- try {
- mActivityRule.finishActivity();
- } catch (IllegalStateException e) {
- // no op
- }
+ /**
+ * Launch test activity with give layout and parameter
+ */
+ protected CustomTestActivity launchActivity(int layoutId, int numViews) {
+ final Intent intent = getLaunchIntent(layoutId, numViews);
+ return (CustomTestActivity) sInstrumentation.startActivitySync(intent);
}
}
diff --git a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/CustomTestActivity.java b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/CustomTestActivity.java
index e509837f441a..c24e79f511bb 100644
--- a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/CustomTestActivity.java
+++ b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/CustomTestActivity.java
@@ -19,6 +19,10 @@ package android.view.contentcapture;
import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
+import android.os.Looper;
+import android.os.RemoteCallback;
+import android.view.Choreographer;
+import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
@@ -31,6 +35,8 @@ import com.android.perftests.contentcapture.R;
public class CustomTestActivity extends Activity {
public static final String INTENT_EXTRA_LAYOUT_ID = "layout_id";
public static final String INTENT_EXTRA_CUSTOM_VIEWS = "custom_view_number";
+ static final String INTENT_EXTRA_FINISH_ON_IDLE = "finish";
+ static final String INTENT_EXTRA_DRAW_CALLBACK = "draw_callback";
public static final int MAX_VIEWS = 500;
private static final int CUSTOM_CONTAINER_LAYOUT_ID = R.layout.test_container_activity;
@@ -47,6 +53,34 @@ public class CustomTestActivity extends Activity {
getIntent().getIntExtra(INTENT_EXTRA_CUSTOM_VIEWS, MAX_VIEWS));
}
}
+
+ final RemoteCallback drawCallback = getIntent().getParcelableExtra(
+ INTENT_EXTRA_DRAW_CALLBACK, RemoteCallback.class);
+ if (drawCallback != null) {
+ getWindow().getDecorView().addOnAttachStateChangeListener(
+ new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ Choreographer.getInstance().postCallback(
+ Choreographer.CALLBACK_COMMIT,
+ // Report that the first frame is drawn.
+ () -> drawCallback.sendResult(null), null /* token */);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ }
+ });
+ }
+
+ if (getIntent().getBooleanExtra(INTENT_EXTRA_FINISH_ON_IDLE, false)) {
+ Looper.myQueue().addIdleHandler(() -> {
+ // Finish without animation.
+ finish();
+ overridePendingTransition(0 /* enterAnim */, 0 /* exitAnim */);
+ return false;
+ });
+ }
}
private void createCustomViews(LinearLayout root, int number) {
diff --git a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/LoginTest.java b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/LoginTest.java
index 725750976d98..aa95dfdfdf16 100644
--- a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/LoginTest.java
+++ b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/LoginTest.java
@@ -15,9 +15,10 @@
*/
package android.view.contentcapture;
-import static com.android.compatibility.common.util.ActivitiesWatcher.ActivityLifecycle.CREATED;
import static com.android.compatibility.common.util.ActivitiesWatcher.ActivityLifecycle.DESTROYED;
+import android.content.Intent;
+import android.os.RemoteCallback;
import android.perftests.utils.BenchmarkState;
import android.view.View;
@@ -80,17 +81,32 @@ public class LoginTest extends AbstractContentCapturePerfTestCase {
}
private void testActivityLaunchTime(int layoutId, int numViews) throws Throwable {
+ final Object drawNotifier = new Object();
+ final Intent intent = getLaunchIntent(layoutId, numViews);
+ intent.putExtra(CustomTestActivity.INTENT_EXTRA_FINISH_ON_IDLE, true);
+ intent.putExtra(CustomTestActivity.INTENT_EXTRA_DRAW_CALLBACK,
+ new RemoteCallback(result -> {
+ synchronized (drawNotifier) {
+ drawNotifier.notifyAll();
+ }
+ }));
final ActivityWatcher watcher = startWatcher();
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- launchActivity(layoutId, numViews);
+ mEntryActivity.startActivity(intent);
+ synchronized (drawNotifier) {
+ try {
+ drawNotifier.wait(GENERIC_TIMEOUT_MS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
// Ignore the time to finish the activity
state.pauseTiming();
- watcher.waitFor(CREATED);
- finishActivity();
watcher.waitFor(DESTROYED);
+ sInstrumentation.waitForIdleSync();
state.resumeTiming();
}
}
@@ -142,12 +158,12 @@ public class LoginTest extends AbstractContentCapturePerfTestCase {
while (state.keepRunning()) {
// Only count the time of onVisibilityAggregated()
state.pauseTiming();
- mActivityRule.runOnUiThread(() -> {
+ sInstrumentation.runOnMainSync(() -> {
state.resumeTiming();
view.onVisibilityAggregated(false);
state.pauseTiming();
});
- mActivityRule.runOnUiThread(() -> {
+ sInstrumentation.runOnMainSync(() -> {
state.resumeTiming();
view.onVisibilityAggregated(true);
state.pauseTiming();
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 9caf99e5b827..19ab5bcc9967 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -30,6 +30,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.app.Notification;
import android.compat.Compatibility;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
@@ -403,6 +404,13 @@ public class JobInfo implements Parcelable {
public static final int FLAG_DATA_TRANSFER = 1 << 5;
/**
+ * Whether it's a user initiated job or not.
+ *
+ * @hide
+ */
+ public static final int FLAG_USER_INITIATED = 1 << 6;
+
+ /**
* @hide
*/
public static final int CONSTRAINT_FLAG_CHARGING = 1 << 0;
@@ -738,6 +746,14 @@ public class JobInfo implements Parcelable {
}
/**
+ * @see JobInfo.Builder#setUserInitiated(boolean)
+ * @hide
+ */
+ public boolean isUserInitiated() {
+ return (flags & FLAG_USER_INITIATED) != 0;
+ }
+
+ /**
* @see JobInfo.Builder#setImportantWhileForeground(boolean)
*/
public boolean isImportantWhileForeground() {
@@ -1849,15 +1865,8 @@ public class JobInfo implements Parcelable {
*
* <p>
* For user-initiated transfers that must be started immediately, call
- * {@link #setExpedited(boolean) setExpedited(true)}. Otherwise, the system may defer the
- * job to a more opportune time. Using {@link #setExpedited(boolean) setExpedited(true)}
- * with this API will only be allowed for foreground apps and when the user has clearly
- * interacted with the app. {@link #setExpedited(boolean) setExpedited(true)} will return
- * {@link JobScheduler#RESULT_FAILURE} for a data transfer job if the app is in the
- * background. Apps that successfully schedule data transfer jobs with
- * {@link #setExpedited(boolean) setExpedited(true)} will not have quotas applied to them,
- * though they may still be stopped for system health or constraint reasons. The system will
- * also give a user the ability to stop a data transfer job via the Task Manager.
+ * {@link #setUserInitiated(boolean) setUserInitiated(true)}. Otherwise, the system may
+ * defer the job to a more opportune time.
*
* <p>
* If you want to perform more than one data transfer job, consider enqueuing multiple
@@ -1877,6 +1886,50 @@ public class JobInfo implements Parcelable {
}
/**
+ * Indicates that this job is being scheduled to fulfill an explicit user request.
+ * As such, user-initiated jobs can only be scheduled when the app is in the foreground
+ * or in a state where launching an activity is allowed, as defined
+ * <a href=
+ * "https://developer.android.com/guide/components/activities/background-starts#exceptions">
+ * here</a>. Attempting to schedule one outside of these conditions will throw a
+ * {@link SecurityException}.
+ *
+ * <p>
+ * This should <b>NOT</b> be used for automatic features.
+ *
+ * <p>
+ * All user-initiated jobs must have an associated notification, set via
+ * {@link JobService#setNotification(JobParameters, int, Notification, int)}, and will be
+ * shown in the Task Manager when running.
+ *
+ * <p>
+ * These jobs will not be subject to quotas and will be started immediately once scheduled
+ * if all constraints are met and the device system health allows for additional tasks.
+ *
+ * @see JobInfo#isUserInitiated()
+ * @hide
+ */
+ @NonNull
+ public Builder setUserInitiated(boolean userInitiated) {
+ if (userInitiated) {
+ mFlags |= FLAG_USER_INITIATED;
+ if (mPriority == PRIORITY_DEFAULT) {
+ // The default priority for UIJs is MAX, but only change this if .setPriority()
+ // hasn't been called yet.
+ mPriority = PRIORITY_MAX;
+ }
+ } else {
+ if (mPriority == PRIORITY_MAX && (mFlags & FLAG_USER_INITIATED) != 0) {
+ // Reset the priority for the job, but only change this if .setPriority()
+ // hasn't been called yet.
+ mPriority = PRIORITY_DEFAULT;
+ }
+ mFlags &= (~FLAG_USER_INITIATED);
+ }
+ return this;
+ }
+
+ /**
* Setting this to true indicates that this job is important while the scheduling app
* is in the foreground or on the temporary whitelist for background restrictions.
* This means that the system will relax doze restrictions on this job during this time.
@@ -2086,10 +2139,12 @@ public class JobInfo implements Parcelable {
}
final boolean isExpedited = (flags & FLAG_EXPEDITED) != 0;
+ final boolean isUserInitiated = (flags & FLAG_USER_INITIATED) != 0;
switch (mPriority) {
case PRIORITY_MAX:
- if (!isExpedited) {
- throw new IllegalArgumentException("Only expedited jobs can have max priority");
+ if (!(isExpedited || isUserInitiated)) {
+ throw new IllegalArgumentException(
+ "Only expedited or user-initiated jobs can have max priority");
}
break;
case PRIORITY_HIGH:
@@ -2118,14 +2173,20 @@ public class JobInfo implements Parcelable {
if (isPeriodic) {
throw new IllegalArgumentException("An expedited job cannot be periodic");
}
+ if ((flags & FLAG_DATA_TRANSFER) != 0) {
+ throw new IllegalArgumentException(
+ "An expedited job cannot also be a data transfer job");
+ }
+ if (isUserInitiated) {
+ throw new IllegalArgumentException("An expedited job cannot be user-initiated");
+ }
if (mPriority != PRIORITY_MAX && mPriority != PRIORITY_HIGH) {
throw new IllegalArgumentException(
"An expedited job must be high or max priority. Don't use expedited jobs"
+ " for unimportant tasks.");
}
- if (((constraintFlags & ~CONSTRAINT_FLAG_STORAGE_NOT_LOW) != 0
- || (flags & ~(FLAG_EXPEDITED | FLAG_EXEMPT_FROM_APP_STANDBY
- | FLAG_DATA_TRANSFER)) != 0)) {
+ if ((constraintFlags & ~CONSTRAINT_FLAG_STORAGE_NOT_LOW) != 0
+ || (flags & ~(FLAG_EXPEDITED | FLAG_EXEMPT_FROM_APP_STANDBY)) != 0) {
throw new IllegalArgumentException(
"An expedited job can only have network and storage-not-low constraints");
}
@@ -2152,6 +2213,33 @@ public class JobInfo implements Parcelable {
"A data transfer job must specify a valid network type");
}
}
+
+ if (isUserInitiated) {
+ if (hasEarlyConstraint) {
+ throw new IllegalArgumentException("A user-initiated job cannot have a time delay");
+ }
+ if (hasLateConstraint) {
+ throw new IllegalArgumentException("A user-initiated job cannot have a deadline");
+ }
+ if (isPeriodic) {
+ throw new IllegalArgumentException("A user-initiated job cannot be periodic");
+ }
+ if ((flags & FLAG_PREFETCH) != 0) {
+ throw new IllegalArgumentException(
+ "A user-initiated job cannot also be a prefetch job");
+ }
+ if (mPriority != PRIORITY_MAX) {
+ throw new IllegalArgumentException("A user-initiated job must be max priority.");
+ }
+ if ((constraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) {
+ throw new IllegalArgumentException(
+ "A user-initiated job cannot have a device-idle constraint");
+ }
+ if (triggerContentUris != null && triggerContentUris.length > 0) {
+ throw new IllegalArgumentException(
+ "Can't call addTriggerContentUri() on a user-initiated job");
+ }
+ }
}
/**
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 30986dde6b91..651853bae68e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -761,8 +761,8 @@ class JobConcurrencyManager {
if (js != null) {
mWorkCountTracker.incrementRunningJobCount(jsc.getRunningJobWorkType());
assignment.workType = jsc.getRunningJobWorkType();
- if (js.startedAsExpeditedJob && js.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
- info.numRunningTopEj++;
+ if (js.startedWithImmediacyPrivilege) {
+ info.numRunningImmediacyPrivileged++;
}
}
@@ -829,11 +829,9 @@ class JobConcurrencyManager {
continue;
}
- final boolean isTopEj = nextPending.shouldTreatAsExpeditedJob()
- && nextPending.lastEvaluatedBias == JobInfo.BIAS_TOP_APP;
+ final boolean hasImmediacyPrivilege = hasImmediacyPrivilegeLocked(nextPending);
if (DEBUG && isSimilarJobRunningLocked(nextPending)) {
- Slog.w(TAG, "Already running similar " + (isTopEj ? "TOP-EJ" : "job")
- + " to: " + nextPending);
+ Slog.w(TAG, "Already running similar job to: " + nextPending);
}
// Factoring minChangedWaitingTimeMs into the min waiting time effectively limits
@@ -876,23 +874,25 @@ class JobConcurrencyManager {
final JobStatus runningJob = assignment.context.getRunningJobLocked();
// Maybe stop the job if it has had its day in the sun. Only allow replacing
// for one of the following conditions:
- // 1. We're putting in the current TOP app's EJ
+ // 1. We're putting in a job that has the privilege of running immediately
// 2. There aren't too many jobs running AND the current job started when the
// app was in the background
// 3. There aren't too many jobs running AND the current job started when the
// app was on TOP, but the app has since left TOP
// 4. There aren't too many jobs running AND the current job started when the
- // app was on TOP, the app is still TOP, but there are too many TOP+EJs
+ // app was on TOP, the app is still TOP, but there are too many
+ // immediacy-privileged jobs
// running (because we don't want them to starve out other apps and the
// current job has already run for the minimum guaranteed time).
// 5. This new job could be waiting for too long for a slot to open up
- boolean canReplace = isTopEj; // Case 1
+ boolean canReplace = hasImmediacyPrivilege; // Case 1
if (!canReplace && !isInOverage) {
final int currentJobBias = mService.evaluateJobBiasLocked(runningJob);
canReplace = runningJob.lastEvaluatedBias < JobInfo.BIAS_TOP_APP // Case 2
|| currentJobBias < JobInfo.BIAS_TOP_APP // Case 3
// Case 4
- || info.numRunningTopEj > .5 * mWorkTypeConfig.getMaxTotal();
+ || info.numRunningImmediacyPrivileged
+ > (mWorkTypeConfig.getMaxTotal() / 2);
}
if (!canReplace && mMaxWaitTimeBypassEnabled) { // Case 5
if (nextPending.shouldTreatAsExpeditedJob()) {
@@ -919,7 +919,7 @@ class JobConcurrencyManager {
}
}
}
- if (selectedContext == null && (!isInOverage || isTopEj)) {
+ if (selectedContext == null && (!isInOverage || hasImmediacyPrivilege)) {
int lowestBiasSeen = Integer.MAX_VALUE;
long newMinPreferredUidOnlyWaitingTimeMs = Long.MAX_VALUE;
for (int p = preferredUidOnly.size() - 1; p >= 0; --p) {
@@ -962,12 +962,13 @@ class JobConcurrencyManager {
info.minPreferredUidOnlyWaitingTimeMs = newMinPreferredUidOnlyWaitingTimeMs;
}
}
- // Make sure to run EJs for the TOP app immediately.
- if (isTopEj) {
+ // Make sure to run jobs with special privilege immediately.
+ if (hasImmediacyPrivilege) {
if (selectedContext != null
&& selectedContext.context.getRunningJobLocked() != null) {
- // We're "replacing" a currently running job, but we want TOP EJs to start
- // immediately, so we'll start the EJ on a fresh available context and
+ // We're "replacing" a currently running job, but we want immediacy-privileged
+ // jobs to start immediately, so we'll start the privileged jobs on a fresh
+ // available context and
// stop this currently running job to replace in two steps.
changed.add(selectedContext);
projectedRunningCount--;
@@ -1029,6 +1030,7 @@ class JobConcurrencyManager {
projectedRunningCount--;
}
if (selectedContext.newJob != null) {
+ selectedContext.newJob.startedWithImmediacyPrivilege = hasImmediacyPrivilege;
projectedRunningCount++;
minChangedWaitingTimeMs = Math.min(minChangedWaitingTimeMs,
mService.getMinJobExecutionGuaranteeMs(selectedContext.newJob));
@@ -1103,6 +1105,18 @@ class JobConcurrencyManager {
mActivePkgStats.forEach(mPackageStatsStagingCountClearer);
}
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ boolean hasImmediacyPrivilegeLocked(@NonNull JobStatus job) {
+ // EJs & user-initiated jobs for the TOP app should run immediately.
+ // However, even for user-initiated jobs, if the app has not recently been in TOP or BAL
+ // state, we don't give the immediacy privilege so that we can try and maintain
+ // reasonably concurrency behavior.
+ return job.lastEvaluatedBias == JobInfo.BIAS_TOP_APP
+ // TODO(): include BAL state for user-initiated jobs
+ && (job.shouldTreatAsExpeditedJob() || job.shouldTreatAsUserInitiated());
+ }
+
@GuardedBy("mLock")
void onUidBiasChangedLocked(int prevBias, int newBias) {
if (prevBias != JobInfo.BIAS_TOP_APP && newBias != JobInfo.BIAS_TOP_APP) {
@@ -1361,7 +1375,7 @@ class JobConcurrencyManager {
mActiveServices.remove(worker);
if (mIdleContexts.size() < MAX_RETAINED_OBJECTS) {
// Don't need to save all new contexts, but keep some extra around in case we need
- // extras for another TOP+EJ overage.
+ // extras for another immediacy privileged overage.
mIdleContexts.add(worker);
} else {
mNumDroppedContexts++;
@@ -1403,7 +1417,8 @@ class JobConcurrencyManager {
}
if (respectConcurrencyLimit) {
worker.clearPreferredUid();
- // We're over the limit (because the TOP app scheduled a lot of EJs), but we should
+ // We're over the limit (because there were a lot of immediacy-privileged jobs
+ // scheduled), but we should
// be able to stop the other jobs soon so don't start running anything new until we
// get back below the limit.
noteConcurrency();
@@ -1627,17 +1642,17 @@ class JobConcurrencyManager {
}
} else if (mWorkCountTracker.getPendingJobCount(WORK_TYPE_EJ) > 0) {
return "blocking " + workTypeToString(WORK_TYPE_EJ) + " queue";
- } else if (js.startedAsExpeditedJob && js.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
- // Try not to let TOP + EJ starve out other apps.
- int topEjCount = 0;
+ } else if (js.startedWithImmediacyPrivilege) {
+ // Try not to let jobs with immediacy privilege starve out other apps.
+ int immediacyPrivilegeCount = 0;
for (int r = mRunningJobs.size() - 1; r >= 0; --r) {
JobStatus j = mRunningJobs.valueAt(r);
- if (j.startedAsExpeditedJob && j.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
- topEjCount++;
+ if (j.startedWithImmediacyPrivilege) {
+ immediacyPrivilegeCount++;
}
}
- if (topEjCount > .5 * mWorkTypeConfig.getMaxTotal()) {
- return "prevent top EJ dominance";
+ if (immediacyPrivilegeCount > mWorkTypeConfig.getMaxTotal() / 2) {
+ return "prevent immediacy privilege dominance";
}
}
// No other pending EJs. Return null so we don't let regular jobs preempt an EJ.
@@ -1688,6 +1703,40 @@ class JobConcurrencyManager {
return foundSome;
}
+ /**
+ * Returns the estimated network bytes if the job is running. Returns {@code null} if the job
+ * isn't running.
+ */
+ @Nullable
+ @GuardedBy("mLock")
+ Pair<Long, Long> getEstimatedNetworkBytesLocked(String pkgName, int uid, int jobId) {
+ for (int i = 0; i < mActiveServices.size(); i++) {
+ final JobServiceContext jc = mActiveServices.get(i);
+ final JobStatus js = jc.getRunningJobLocked();
+ if (js != null && js.matches(uid, jobId) && js.getSourcePackageName().equals(pkgName)) {
+ return jc.getEstimatedNetworkBytes();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the transferred network bytes if the job is running. Returns {@code null} if the job
+ * isn't running.
+ */
+ @Nullable
+ @GuardedBy("mLock")
+ Pair<Long, Long> getTransferredNetworkBytesLocked(String pkgName, int uid, int jobId) {
+ for (int i = 0; i < mActiveServices.size(); i++) {
+ final JobServiceContext jc = mActiveServices.get(i);
+ final JobStatus js = jc.getRunningJobLocked();
+ if (js != null && js.matches(uid, jobId) && js.getSourcePackageName().equals(pkgName)) {
+ return jc.getTransferredNetworkBytes();
+ }
+ }
+ return null;
+ }
+
@NonNull
private JobServiceContext createNewJobServiceContext() {
return mInjector.createJobServiceContext(mService, this, mNotificationCoordinator,
@@ -2566,11 +2615,11 @@ class JobConcurrencyManager {
@VisibleForTesting
static final class AssignmentInfo {
public long minPreferredUidOnlyWaitingTimeMs;
- public int numRunningTopEj;
+ public int numRunningImmediacyPrivileged;
void clear() {
minPreferredUidOnlyWaitingTimeMs = 0;
- numRunningTopEj = 0;
+ numRunningImmediacyPrivileged = 0;
}
}
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 535f8d4cad45..e9b966083851 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -86,6 +86,7 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -2850,8 +2851,8 @@ public class JobSchedulerService extends com.android.server.SystemService
}
final boolean shouldForceBatchJob;
- if (job.shouldTreatAsExpeditedJob()) {
- // Never batch expedited jobs, even for RESTRICTED apps.
+ if (job.shouldTreatAsExpeditedJob() || job.shouldTreatAsUserInitiated()) {
+ // Never batch expedited or user-initiated jobs, even for RESTRICTED apps.
shouldForceBatchJob = false;
} else if (job.getEffectiveStandbyBucket() == RESTRICTED_INDEX) {
// Restricted jobs must always be batched
@@ -4168,6 +4169,100 @@ public class JobSchedulerService extends com.android.server.SystemService
}
}
+ int getEstimatedNetworkBytes(PrintWriter pw, String pkgName, int userId, int jobId,
+ int byteOption) {
+ try {
+ final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0,
+ userId != UserHandle.USER_ALL ? userId : UserHandle.USER_SYSTEM);
+ if (uid < 0) {
+ pw.print("unknown(");
+ pw.print(pkgName);
+ pw.println(")");
+ return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE;
+ }
+
+ synchronized (mLock) {
+ final JobStatus js = mJobs.getJobByUidAndJobId(uid, jobId);
+ if (DEBUG) {
+ Slog.d(TAG, "get-estimated-network-bytes " + uid + "/" + jobId + ": " + js);
+ }
+ if (js == null) {
+ pw.print("unknown("); UserHandle.formatUid(pw, uid);
+ pw.print("/jid"); pw.print(jobId); pw.println(")");
+ return JobSchedulerShellCommand.CMD_ERR_NO_JOB;
+ }
+
+ final long downloadBytes;
+ final long uploadBytes;
+ final Pair<Long, Long> bytes =
+ mConcurrencyManager.getEstimatedNetworkBytesLocked(pkgName, uid, jobId);
+ if (bytes == null) {
+ downloadBytes = js.getEstimatedNetworkDownloadBytes();
+ uploadBytes = js.getEstimatedNetworkUploadBytes();
+ } else {
+ downloadBytes = bytes.first;
+ uploadBytes = bytes.second;
+ }
+ if (byteOption == JobSchedulerShellCommand.BYTE_OPTION_DOWNLOAD) {
+ pw.println(downloadBytes);
+ } else {
+ pw.println(uploadBytes);
+ }
+ pw.println();
+ }
+ } catch (RemoteException e) {
+ // can't happen
+ }
+ return 0;
+ }
+
+ int getTransferredNetworkBytes(PrintWriter pw, String pkgName, int userId, int jobId,
+ int byteOption) {
+ try {
+ final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0,
+ userId != UserHandle.USER_ALL ? userId : UserHandle.USER_SYSTEM);
+ if (uid < 0) {
+ pw.print("unknown(");
+ pw.print(pkgName);
+ pw.println(")");
+ return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE;
+ }
+
+ synchronized (mLock) {
+ final JobStatus js = mJobs.getJobByUidAndJobId(uid, jobId);
+ if (DEBUG) {
+ Slog.d(TAG, "get-transferred-network-bytes " + uid + "/" + jobId + ": " + js);
+ }
+ if (js == null) {
+ pw.print("unknown("); UserHandle.formatUid(pw, uid);
+ pw.print("/jid"); pw.print(jobId); pw.println(")");
+ return JobSchedulerShellCommand.CMD_ERR_NO_JOB;
+ }
+
+ final long downloadBytes;
+ final long uploadBytes;
+ final Pair<Long, Long> bytes =
+ mConcurrencyManager.getTransferredNetworkBytesLocked(pkgName, uid, jobId);
+ if (bytes == null) {
+ downloadBytes = 0;
+ uploadBytes = 0;
+ } else {
+ downloadBytes = bytes.first;
+ uploadBytes = bytes.second;
+ }
+ if (byteOption == JobSchedulerShellCommand.BYTE_OPTION_DOWNLOAD) {
+ pw.println(downloadBytes);
+ } else {
+ pw.println(uploadBytes);
+ }
+ pw.println();
+ }
+ } catch (RemoteException e) {
+ // can't happen
+ }
+ return 0;
+ }
+
private boolean checkRunLongJobsPermission(int packageUid, String packageName) {
// Returns true if both the appop and permission are granted.
return PermissionChecker.checkPermissionForPreflight(getTestableContext(),
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
index 27268d267001..36ba8dd10bd5 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -32,6 +32,9 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler {
public static final int CMD_ERR_NO_JOB = -1001;
public static final int CMD_ERR_CONSTRAINTS = -1002;
+ static final int BYTE_OPTION_DOWNLOAD = 0;
+ static final int BYTE_OPTION_UPLOAD = 1;
+
JobSchedulerService mInternal;
IPackageManager mPM;
@@ -59,10 +62,18 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler {
return getBatteryCharging(pw);
case "get-battery-not-low":
return getBatteryNotLow(pw);
+ case "get-estimated-download-bytes":
+ return getEstimatedNetworkBytes(pw, BYTE_OPTION_DOWNLOAD);
+ case "get-estimated-upload-bytes":
+ return getEstimatedNetworkBytes(pw, BYTE_OPTION_UPLOAD);
case "get-storage-seq":
return getStorageSeq(pw);
case "get-storage-not-low":
return getStorageNotLow(pw);
+ case "get-transferred-download-bytes":
+ return getTransferredNetworkBytes(pw, BYTE_OPTION_DOWNLOAD);
+ case "get-transferred-upload-bytes":
+ return getTransferredNetworkBytes(pw, BYTE_OPTION_UPLOAD);
case "get-job-state":
return getJobState(pw);
case "heartbeat":
@@ -304,6 +315,43 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler {
return 0;
}
+ private int getEstimatedNetworkBytes(PrintWriter pw, int byteOption) throws Exception {
+ checkPermission("get estimated bytes");
+
+ int userId = UserHandle.USER_SYSTEM;
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-u":
+ case "--user":
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ break;
+
+ default:
+ pw.println("Error: unknown option '" + opt + "'");
+ return -1;
+ }
+ }
+
+ if (userId == UserHandle.USER_CURRENT) {
+ userId = ActivityManager.getCurrentUser();
+ }
+
+ final String pkgName = getNextArgRequired();
+ final String jobIdStr = getNextArgRequired();
+ final int jobId = Integer.parseInt(jobIdStr);
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ int ret = mInternal.getEstimatedNetworkBytes(pw, pkgName, userId, jobId, byteOption);
+ printError(ret, pkgName, userId, jobId);
+ return ret;
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
private int getStorageSeq(PrintWriter pw) {
int seq = mInternal.getStorageSeq();
pw.println(seq);
@@ -316,8 +364,45 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler {
return 0;
}
+ private int getTransferredNetworkBytes(PrintWriter pw, int byteOption) throws Exception {
+ checkPermission("get transferred bytes");
+
+ int userId = UserHandle.USER_SYSTEM;
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-u":
+ case "--user":
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ break;
+
+ default:
+ pw.println("Error: unknown option '" + opt + "'");
+ return -1;
+ }
+ }
+
+ if (userId == UserHandle.USER_CURRENT) {
+ userId = ActivityManager.getCurrentUser();
+ }
+
+ final String pkgName = getNextArgRequired();
+ final String jobIdStr = getNextArgRequired();
+ final int jobId = Integer.parseInt(jobIdStr);
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ int ret = mInternal.getTransferredNetworkBytes(pw, pkgName, userId, jobId, byteOption);
+ printError(ret, pkgName, userId, jobId);
+ return ret;
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
private int getJobState(PrintWriter pw) throws Exception {
- checkPermission("force timeout jobs");
+ checkPermission("get job state");
int userId = UserHandle.USER_SYSTEM;
@@ -473,10 +558,30 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler {
pw.println(" Return whether the battery is currently considered to be charging.");
pw.println(" get-battery-not-low");
pw.println(" Return whether the battery is currently considered to not be low.");
+ pw.println(" get-estimated-download-bytes [-u | --user USER_ID] PACKAGE JOB_ID");
+ pw.println(" Return the most recent estimated download bytes for the job.");
+ pw.println(" Options:");
+ pw.println(" -u or --user: specify which user's job is to be run; the default is");
+ pw.println(" the primary or system user");
+ pw.println(" get-estimated-upload-bytes [-u | --user USER_ID] PACKAGE JOB_ID");
+ pw.println(" Return the most recent estimated upload bytes for the job.");
+ pw.println(" Options:");
+ pw.println(" -u or --user: specify which user's job is to be run; the default is");
+ pw.println(" the primary or system user");
pw.println(" get-storage-seq");
pw.println(" Return the last storage update sequence number that was received.");
pw.println(" get-storage-not-low");
pw.println(" Return whether storage is currently considered to not be low.");
+ pw.println(" get-transferred-download-bytes [-u | --user USER_ID] PACKAGE JOB_ID");
+ pw.println(" Return the most recent transferred download bytes for the job.");
+ pw.println(" Options:");
+ pw.println(" -u or --user: specify which user's job is to be run; the default is");
+ pw.println(" the primary or system user");
+ pw.println(" get-transferred-upload-bytes [-u | --user USER_ID] PACKAGE JOB_ID");
+ pw.println(" Return the most recent transferred upload bytes for the job.");
+ pw.println(" Options:");
+ pw.println(" -u or --user: specify which user's job is to be run; the default is");
+ pw.println(" the primary or system user");
pw.println(" get-job-state [-u | --user USER_ID] PACKAGE JOB_ID");
pw.println(" Return the current state of a job, may be any combination of:");
pw.println(" pending: currently on the pending list, waiting to be active");
@@ -493,5 +598,4 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler {
pw.println(" Trigger wireless charging dock state. Active by default.");
pw.println();
}
-
}
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 df47f1787fdc..0dcfd24b263c 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -49,6 +49,7 @@ import android.os.Trace;
import android.os.UserHandle;
import android.util.EventLog;
import android.util.IndentingPrintWriter;
+import android.util.Pair;
import android.util.Slog;
import android.util.TimeUtils;
@@ -171,6 +172,11 @@ public final class JobServiceContext implements ServiceConnection {
/** The absolute maximum amount of time the job can run */
private long mMaxExecutionTimeMillis;
+ private long mEstimatedDownloadBytes;
+ private long mEstimatedUploadBytes;
+ private long mTransferredDownloadBytes;
+ private long mTransferredUploadBytes;
+
/**
* The stop reason for a pending cancel. If there's not pending cancel, then the value should be
* {@link JobParameters#STOP_REASON_UNDEFINED}.
@@ -306,6 +312,9 @@ public final class JobServiceContext implements ServiceConnection {
mMinExecutionGuaranteeMillis = mService.getMinJobExecutionGuaranteeMs(job);
mMaxExecutionTimeMillis =
Math.max(mService.getMaxJobExecutionTimeMs(job), mMinExecutionGuaranteeMillis);
+ mEstimatedDownloadBytes = job.getEstimatedNetworkDownloadBytes();
+ mEstimatedUploadBytes = job.getEstimatedNetworkUploadBytes();
+ mTransferredDownloadBytes = mTransferredUploadBytes = 0;
final long whenDeferred = job.getWhenStandbyDeferred();
if (whenDeferred > 0) {
@@ -524,6 +533,16 @@ public final class JobServiceContext implements ServiceConnection {
return false;
}
+ @GuardedBy("mLock")
+ Pair<Long, Long> getEstimatedNetworkBytes() {
+ return Pair.create(mEstimatedDownloadBytes, mEstimatedUploadBytes);
+ }
+
+ @GuardedBy("mLock")
+ Pair<Long, Long> getTransferredNetworkBytes() {
+ return Pair.create(mTransferredDownloadBytes, mTransferredUploadBytes);
+ }
+
void doJobFinished(JobCallback cb, int jobId, boolean reschedule) {
final long ident = Binder.clearCallingIdentity();
try {
@@ -541,14 +560,26 @@ public final class JobServiceContext implements ServiceConnection {
}
}
- private void doAcknowledgeGetTransferredDownloadBytesMessage(JobCallback jobCallback, int jobId,
+ private void doAcknowledgeGetTransferredDownloadBytesMessage(JobCallback cb, int jobId,
int workId, @BytesLong long transferredBytes) {
// TODO(255393346): Make sure apps call this appropriately and monitor for abuse
+ synchronized (mLock) {
+ if (!verifyCallerLocked(cb)) {
+ return;
+ }
+ mTransferredDownloadBytes = transferredBytes;
+ }
}
- private void doAcknowledgeGetTransferredUploadBytesMessage(JobCallback jobCallback, int jobId,
+ private void doAcknowledgeGetTransferredUploadBytesMessage(JobCallback cb, int jobId,
int workId, @BytesLong long transferredBytes) {
// TODO(255393346): Make sure apps call this appropriately and monitor for abuse
+ synchronized (mLock) {
+ if (!verifyCallerLocked(cb)) {
+ return;
+ }
+ mTransferredUploadBytes = transferredBytes;
+ }
}
void doAcknowledgeStopMessage(JobCallback cb, int jobId, boolean reschedule) {
@@ -603,6 +634,30 @@ public final class JobServiceContext implements ServiceConnection {
}
}
+ private void doUpdateEstimatedNetworkBytes(JobCallback cb, int jobId,
+ @Nullable JobWorkItem item, long downloadBytes, long uploadBytes) {
+ // TODO(255393346): Make sure apps call this appropriately and monitor for abuse
+ synchronized (mLock) {
+ if (!verifyCallerLocked(cb)) {
+ return;
+ }
+ mEstimatedDownloadBytes = downloadBytes;
+ mEstimatedUploadBytes = uploadBytes;
+ }
+ }
+
+ private void doUpdateTransferredNetworkBytes(JobCallback cb, int jobId,
+ @Nullable JobWorkItem item, long downloadBytes, long uploadBytes) {
+ // TODO(255393346): Make sure apps call this appropriately and monitor for abuse
+ synchronized (mLock) {
+ if (!verifyCallerLocked(cb)) {
+ return;
+ }
+ mTransferredDownloadBytes = downloadBytes;
+ mTransferredUploadBytes = uploadBytes;
+ }
+ }
+
private void doSetNotification(JobCallback cb, int jodId, int notificationId,
Notification notification, int jobEndNotificationPolicy) {
final int callingPid = Binder.getCallingPid();
@@ -627,16 +682,6 @@ public final class JobServiceContext implements ServiceConnection {
}
}
- private void doUpdateTransferredNetworkBytes(JobCallback jobCallback, int jobId,
- @Nullable JobWorkItem item, long downloadBytes, long uploadBytes) {
- // TODO(255393346): Make sure apps call this appropriately and monitor for abuse
- }
-
- private void doUpdateEstimatedNetworkBytes(JobCallback jobCallback, int jobId,
- @Nullable JobWorkItem item, long downloadBytes, long uploadBytes) {
- // TODO(255393346): Make sure apps call this appropriately and monitor for abuse
- }
-
/**
* We acquire/release a wakelock on onServiceConnected/unbindService. This mirrors the work
* we intend to send to the client - we stop sending work when the service is unbound so until
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index 6166921d64b2..3610b0a0064b 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -149,11 +149,13 @@ public final class ConnectivityController extends RestrictingController implemen
// 2. Waiting connectivity jobs would be ready with connectivity
// 3. An existing network satisfies a waiting connectivity job's requirements
// 4. TOP proc state
- // 5. Existence of treat-as-EJ EJs (not just requested EJs)
- // 6. FGS proc state
- // 7. EJ enqueue time
- // 8. Any other important job priorities/proc states
- // 9. Enqueue time
+ // 5. Existence of treat-as-UI UIJs (not just requested UIJs)
+ // 6. Existence of treat-as-EJ EJs (not just requested EJs)
+ // 7. FGS proc state
+ // 8. UIJ enqueue time
+ // 9. EJ enqueue time
+ // 10. Any other important job priorities/proc states
+ // 11. Enqueue time
// TODO: maybe consider number of jobs
// TODO: consider IMPORTANT_WHILE_FOREGROUND bit
final int runningPriority = prioritizeExistenceOver(0,
@@ -181,8 +183,13 @@ public final class ConnectivityController extends RestrictingController implemen
if (topPriority != 0) {
return topPriority;
}
- // They're either both TOP or both not TOP. Prioritize the app that has runnable EJs
+ // They're either both TOP or both not TOP. Prioritize the app that has runnable UIJs
// pending.
+ final int uijPriority = prioritizeExistenceOver(0, us1.numUIJs, us2.numUIJs);
+ if (uijPriority != 0) {
+ return uijPriority;
+ }
+ // Still equivalent. Prioritize the app that has runnable EJs pending.
final int ejPriority = prioritizeExistenceOver(0, us1.numEJs, us2.numEJs);
if (ejPriority != 0) {
return ejPriority;
@@ -195,6 +202,12 @@ public final class ConnectivityController extends RestrictingController implemen
if (fgsPriority != 0) {
return fgsPriority;
}
+ // Order them by UIJ enqueue time to help provide low UIJ latency.
+ if (us1.earliestUIJEnqueueTime < us2.earliestUIJEnqueueTime) {
+ return -1;
+ } else if (us1.earliestUIJEnqueueTime > us2.earliestUIJEnqueueTime) {
+ return 1;
+ }
// Order them by EJ enqueue time to help provide low EJ latency.
if (us1.earliestEJEnqueueTime < us2.earliestEJEnqueueTime) {
return -1;
@@ -414,7 +427,7 @@ public final class ConnectivityController extends RestrictingController implemen
final UidStats uidStats =
getUidStats(jobStatus.getSourceUid(), jobStatus.getSourcePackageName(), true);
- if (jobStatus.shouldTreatAsExpeditedJob()) {
+ if (jobStatus.shouldTreatAsExpeditedJob() && jobStatus.shouldTreatAsUserInitiated()) {
if (!jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)) {
// Don't request a direct hole through any of the firewalls. Instead, mark the
// constraint as satisfied if the network is available, and the job will get
@@ -936,10 +949,12 @@ public final class ConnectivityController extends RestrictingController implemen
if (us.lastUpdatedElapsed + MIN_STATS_UPDATE_INTERVAL_MS < nowElapsed) {
us.earliestEnqueueTime = Long.MAX_VALUE;
us.earliestEJEnqueueTime = Long.MAX_VALUE;
+ us.earliestUIJEnqueueTime = Long.MAX_VALUE;
us.numReadyWithConnectivity = 0;
us.numRequestedNetworkAvailable = 0;
us.numRegular = 0;
us.numEJs = 0;
+ us.numUIJs = 0;
for (int j = 0; j < jobs.size(); ++j) {
JobStatus job = jobs.valueAt(j);
@@ -956,10 +971,15 @@ public final class ConnectivityController extends RestrictingController implemen
if (job.shouldTreatAsExpeditedJob() || job.startedAsExpeditedJob) {
us.earliestEJEnqueueTime =
Math.min(us.earliestEJEnqueueTime, job.enqueueTime);
+ } else if (job.shouldTreatAsUserInitiated()) {
+ us.earliestUIJEnqueueTime =
+ Math.min(us.earliestUIJEnqueueTime, job.enqueueTime);
}
}
if (job.shouldTreatAsExpeditedJob() || job.startedAsExpeditedJob) {
us.numEJs++;
+ } else if (job.shouldTreatAsUserInitiated()) {
+ us.numUIJs++;
} else {
us.numRegular++;
}
@@ -1466,8 +1486,10 @@ public final class ConnectivityController extends RestrictingController implemen
public int numRequestedNetworkAvailable;
public int numEJs;
public int numRegular;
+ public int numUIJs;
public long earliestEnqueueTime;
public long earliestEJEnqueueTime;
+ public long earliestUIJEnqueueTime;
public long lastUpdatedElapsed;
private UidStats(int uid) {
@@ -1485,6 +1507,7 @@ public final class ConnectivityController extends RestrictingController implemen
pw.print("#reg", numRegular);
pw.print("earliestEnqueue", earliestEnqueueTime);
pw.print("earliestEJEnqueue", earliestEJEnqueueTime);
+ pw.print("earliestUIJEnqueue", earliestUIJEnqueueTime);
pw.print("updated=");
TimeUtils.formatDuration(lastUpdatedElapsed - nowElapsed, pw);
pw.println("}");
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 419127e6c6a9..251a4dac3685 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
@@ -388,6 +388,8 @@ public final class JobStatus {
*/
public boolean startedAsExpeditedJob = false;
+ public boolean startedWithImmediacyPrivilege = false;
+
// If non-null, this is work that has been enqueued for the job.
public ArrayList<JobWorkItem> pendingWork;
@@ -1369,9 +1371,7 @@ public final class JobStatus {
* @return true if this is a job whose execution should be made visible to the user.
*/
public boolean isUserVisibleJob() {
- // TODO(255767350): limit to user-initiated jobs
- // Placeholder implementation until we have the code in
- return shouldTreatAsExpeditedJob();
+ return shouldTreatAsUserInitiated();
}
/**
@@ -1382,12 +1382,14 @@ public final class JobStatus {
return appHasDozeExemption
|| (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0
|| ((shouldTreatAsExpeditedJob() || startedAsExpeditedJob)
+ || shouldTreatAsUserInitiated()
&& (mDynamicConstraints & CONSTRAINT_DEVICE_NOT_DOZING) == 0);
}
boolean canRunInBatterySaver() {
return (getInternalFlags() & INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION) != 0
|| ((shouldTreatAsExpeditedJob() || startedAsExpeditedJob)
+ || shouldTreatAsUserInitiated()
&& (mDynamicConstraints & CONSTRAINT_BACKGROUND_NOT_RESTRICTED) == 0);
}
diff --git a/core/api/current.txt b/core/api/current.txt
index c14ac950000c..a7bca5a9e09c 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4150,6 +4150,8 @@ package android.app {
method @Deprecated public android.app.FragmentManager getFragmentManager();
method public android.content.Intent getIntent();
method @Nullable public Object getLastNonConfigurationInstance();
+ method @Nullable public String getLaunchedFromPackage();
+ method public int getLaunchedFromUid();
method @NonNull public android.view.LayoutInflater getLayoutInflater();
method @Deprecated public android.app.LoaderManager getLoaderManager();
method @NonNull public String getLocalClassName();
@@ -4622,6 +4624,7 @@ package android.app {
method public android.app.ActivityOptions setLaunchDisplayId(int);
method public android.app.ActivityOptions setLockTaskEnabled(boolean);
method public void setPendingIntentBackgroundActivityLaunchAllowed(boolean);
+ method @NonNull public android.app.ActivityOptions setShareIdentityEnabled(boolean);
method @NonNull public android.app.ActivityOptions setSplashScreenStyle(int);
method public android.os.Bundle toBundle();
method public void update(android.app.ActivityOptions);
@@ -20526,6 +20529,7 @@ package android.media {
field public static final int ENCODING_DOLBY_MAT = 19; // 0x13
field public static final int ENCODING_DOLBY_TRUEHD = 14; // 0xe
field public static final int ENCODING_DRA = 28; // 0x1c
+ field public static final int ENCODING_DSD = 31; // 0x1f
field public static final int ENCODING_DTS = 7; // 0x7
field public static final int ENCODING_DTS_HD = 8; // 0x8
field public static final int ENCODING_DTS_HD_MA = 29; // 0x1d
@@ -20919,6 +20923,7 @@ package android.media {
method public void writeToParcel(@NonNull android.os.Parcel, int);
field public static final int AUDIO_ENCAPSULATION_TYPE_IEC61937 = 1; // 0x1
field public static final int AUDIO_ENCAPSULATION_TYPE_NONE = 0; // 0x0
+ field public static final int AUDIO_ENCAPSULATION_TYPE_PCM = 2; // 0x2
field @NonNull public static final android.os.Parcelable.Creator<android.media.AudioProfile> CREATOR;
}
@@ -25460,11 +25465,21 @@ package android.media.projection {
public abstract static class MediaProjection.Callback {
ctor public MediaProjection.Callback();
+ method public void onCapturedContentResize(int, int);
method public void onStop();
}
+ public final class MediaProjectionConfig implements android.os.Parcelable {
+ method @NonNull public static android.media.projection.MediaProjectionConfig createConfigForDisplay(@IntRange(from=android.view.Display.DEFAULT_DISPLAY, to=android.view.Display.DEFAULT_DISPLAY) int);
+ method @NonNull public static android.media.projection.MediaProjectionConfig createConfigForUserChoice();
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.projection.MediaProjectionConfig> CREATOR;
+ }
+
public final class MediaProjectionManager {
- method public android.content.Intent createScreenCaptureIntent();
+ method @NonNull public android.content.Intent createScreenCaptureIntent();
+ method @NonNull public android.content.Intent createScreenCaptureIntent(@NonNull android.media.projection.MediaProjectionConfig);
method public android.media.projection.MediaProjection getMediaProjection(int, @NonNull android.content.Intent);
}
@@ -27881,13 +27896,17 @@ package android.net.vcn {
method @IntRange(from=0x500) public int getMaxMtu();
method @NonNull public long[] getRetryIntervalsMillis();
method @NonNull public java.util.List<android.net.vcn.VcnUnderlyingNetworkTemplate> getVcnUnderlyingNetworkPriorities();
+ method public boolean hasGatewayOption(int);
+ field public static final int VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY = 0; // 0x0
}
public static final class VcnGatewayConnectionConfig.Builder {
ctor public VcnGatewayConnectionConfig.Builder(@NonNull String, @NonNull android.net.ipsec.ike.IkeTunnelConnectionParams);
method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addExposedCapability(int);
+ method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addGatewayOption(int);
method @NonNull public android.net.vcn.VcnGatewayConnectionConfig build();
method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeExposedCapability(int);
+ method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeGatewayOption(int);
method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setMaxMtu(@IntRange(from=0x500) int);
method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setRetryIntervalsMillis(@NonNull long[]);
method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setVcnUnderlyingNetworkPriorities(@NonNull java.util.List<android.net.vcn.VcnUnderlyingNetworkTemplate>);
@@ -38277,7 +38296,7 @@ package android.security.identity {
ctor public AlreadyPersonalizedException(@NonNull String, @NonNull Throwable);
}
- public class AuthenticationKeyMetadata {
+ public final class AuthenticationKeyMetadata {
method @NonNull public java.time.Instant getExpirationDate();
method @IntRange(from=0) public int getUsageCount();
}
@@ -52785,12 +52804,14 @@ package android.view.accessibility {
method public void addAudioDescriptionRequestedChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.accessibility.AccessibilityManager.AudioDescriptionRequestedChangeListener);
method public boolean addTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
method public void addTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener, @Nullable android.os.Handler);
+ method public void addUiContrastChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.accessibility.AccessibilityManager.UiContrastChangeListener);
method @ColorInt public int getAccessibilityFocusColor();
method public int getAccessibilityFocusStrokeWidth();
method @Deprecated public java.util.List<android.content.pm.ServiceInfo> getAccessibilityServiceList();
method public java.util.List<android.accessibilityservice.AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int);
method public java.util.List<android.accessibilityservice.AccessibilityServiceInfo> getInstalledAccessibilityServiceList();
method public int getRecommendedTimeoutMillis(int, int);
+ method @FloatRange(from=-1.0F, to=1.0f) public float getUiContrast();
method public void interrupt();
method public static boolean isAccessibilityButtonSupported();
method public boolean isAudioDescriptionRequested();
@@ -52802,6 +52823,7 @@ package android.view.accessibility {
method public boolean removeAccessibilityStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
method public boolean removeAudioDescriptionRequestedChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AudioDescriptionRequestedChangeListener);
method public boolean removeTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
+ method public void removeUiContrastChangeListener(@NonNull android.view.accessibility.AccessibilityManager.UiContrastChangeListener);
method public void sendAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
field public static final int FLAG_CONTENT_CONTROLS = 4; // 0x4
field public static final int FLAG_CONTENT_ICONS = 1; // 0x1
@@ -52824,6 +52846,10 @@ package android.view.accessibility {
method public void onTouchExplorationStateChanged(boolean);
}
+ public static interface AccessibilityManager.UiContrastChangeListener {
+ method public void onUiContrastChanged(@FloatRange(from=-1.0F, to=1.0f) float);
+ }
+
public class AccessibilityNodeInfo implements android.os.Parcelable {
ctor public AccessibilityNodeInfo();
ctor public AccessibilityNodeInfo(@NonNull android.view.View);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 5e4834a04a83..92722901e485 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -352,6 +352,7 @@ package android {
field public static final String USE_COLORIZED_NOTIFICATIONS = "android.permission.USE_COLORIZED_NOTIFICATIONS";
field public static final String USE_RESERVED_DISK = "android.permission.USE_RESERVED_DISK";
field public static final String UWB_PRIVILEGED = "android.permission.UWB_PRIVILEGED";
+ field public static final String WAKEUP_SURFACE_FLINGER = "android.permission.WAKEUP_SURFACE_FLINGER";
field public static final String WHITELIST_AUTO_REVOKE_PERMISSIONS = "android.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS";
field public static final String WHITELIST_RESTRICTED_PERMISSIONS = "android.permission.WHITELIST_RESTRICTED_PERMISSIONS";
field public static final String WIFI_ACCESS_COEX_UNSAFE_CHANNELS = "android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS";
@@ -388,6 +389,7 @@ package android {
field public static final int sdkVersion = 16844304; // 0x1010610
field public static final int supportsAmbientMode = 16844173; // 0x101058d
field public static final int userRestriction = 16844164; // 0x1010584
+ field public static final int visualQueryDetectionService;
}
public static final class R.bool {
@@ -2999,6 +3001,7 @@ package android.companion.virtual {
method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualKeyboard createVirtualKeyboard(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.input.VirtualMouseConfig);
method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
+ method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualNavigationTouchpad createVirtualNavigationTouchpad(@NonNull android.hardware.input.VirtualNavigationTouchpadConfig);
method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.input.VirtualTouchscreenConfig);
method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
method public int getDeviceId();
@@ -3607,6 +3610,7 @@ package android.content.pm {
public abstract class PackageManager {
method @RequiresPermission("android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS") public abstract void addOnPermissionsChangeListener(@NonNull android.content.pm.PackageManager.OnPermissionsChangedListener);
method public abstract boolean arePermissionsIndividuallyControlled();
+ method @NonNull public boolean[] canPackageQuery(@NonNull String, @NonNull String[]) throws android.content.pm.PackageManager.NameNotFoundException;
method @NonNull public abstract java.util.List<android.content.IntentFilter> getAllIntentFilters(@NonNull String);
method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.pm.ApplicationInfo getApplicationInfoAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.pm.ApplicationInfo getApplicationInfoAsUser(@NonNull String, @NonNull android.content.pm.PackageManager.ApplicationInfoFlags, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -4783,6 +4787,24 @@ package android.hardware.input {
method @NonNull public android.hardware.input.VirtualMouseScrollEvent.Builder setYAxisMovement(@FloatRange(from=-1.0F, to=1.0f) float);
}
+ public class VirtualNavigationTouchpad implements java.io.Closeable {
+ method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
+ method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendTouchEvent(@NonNull android.hardware.input.VirtualTouchEvent);
+ }
+
+ public final class VirtualNavigationTouchpadConfig extends android.hardware.input.VirtualInputDeviceConfig implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getHeight();
+ method public int getWidth();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualNavigationTouchpadConfig> CREATOR;
+ }
+
+ public static final class VirtualNavigationTouchpadConfig.Builder extends android.hardware.input.VirtualInputDeviceConfig.Builder<android.hardware.input.VirtualNavigationTouchpadConfig.Builder> {
+ ctor public VirtualNavigationTouchpadConfig.Builder(@IntRange(from=1) int, @IntRange(from=1) int);
+ method @NonNull public android.hardware.input.VirtualNavigationTouchpadConfig build();
+ }
+
public final class VirtualTouchEvent implements android.os.Parcelable {
method public int describeContents();
method public int getAction();
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 9730c169243c..5e02e72f2088 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2944,6 +2944,7 @@ package android.view {
}
public final class MotionEvent extends android.view.InputEvent implements android.os.Parcelable {
+ method public int getDisplayId();
method public void setActionButton(int);
method public void setButtonState(int);
method public void setDisplayId(int);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index a16d4ba7a386..b9eb44316956 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -6654,16 +6654,64 @@ public class Activity extends ContextThemeWrapper
}
/**
- * Returns the uid who started this activity.
- * @hide
+ * Returns the uid of the app that initially launched this activity.
+ *
+ * <p>In order to receive the launching app's uid, at least one of the following has to
+ * be met:
+ * <ul>
+ * <li>The app must call {@link ActivityOptions#setShareIdentityEnabled(boolean)} with a
+ * value of {@code true} and launch this activity with the resulting {@code
+ * ActivityOptions}.
+ * <li>The launched activity has the same uid as the launching app.
+ * <li>The launched activity is running in a package that is signed with the same key
+ * used to sign the platform (typically only system packages such as Settings will
+ * meet this requirement).
+ * </ul>.
+ * These are the same requirements for {@link #getLaunchedFromPackage()}; if any of these are
+ * met, then these methods can be used to obtain the uid and package name of the launching
+ * app. If none are met, then {@link Process#INVALID_UID} is returned.
+ *
+ * <p>Note, even if the above conditions are not met, the launching app's identity may
+ * still be available from {@link #getCallingPackage()} if this activity was started with
+ * {@code Activity#startActivityForResult} to allow validation of the result's recipient.
+ *
+ * @return the uid of the launching app or {@link Process#INVALID_UID} if the current
+ * activity cannot access the identity of the launching app
+ *
+ * @see ActivityOptions#setShareIdentityEnabled(boolean)
+ * @see #getLaunchedFromPackage()
*/
public int getLaunchedFromUid() {
return ActivityClient.getInstance().getLaunchedFromUid(getActivityToken());
}
/**
- * Returns the package who started this activity.
- * @hide
+ * Returns the package name of the app that initially launched this activity.
+ *
+ * <p>In order to receive the launching app's package name, at least one of the following has
+ * to be met:
+ * <ul>
+ * <li>The app must call {@link ActivityOptions#setShareIdentityEnabled(boolean)} with a
+ * value of {@code true} and launch this activity with the resulting
+ * {@code ActivityOptions}.
+ * <li>The launched activity has the same uid as the launching app.
+ * <li>The launched activity is running in a package that is signed with the same key
+ * used to sign the platform (typically only system packages such as Settings will
+ * meet this requirement).
+ * </ul>.
+ * These are the same requirements for {@link #getLaunchedFromUid()}; if any of these are
+ * met, then these methods can be used to obtain the uid and package name of the launching
+ * app. If none are met, then {@code null} is returned.
+ *
+ * <p>Note, even if the above conditions are not met, the launching app's identity may
+ * still be available from {@link #getCallingPackage()} if this activity was started with
+ * {@code Activity#startActivityForResult} to allow validation of the result's recipient.
+ *
+ * @return the package name of the launching app or null if the current activity
+ * cannot access the identity of the launching app
+ *
+ * @see ActivityOptions#setShareIdentityEnabled(boolean)
+ * @see #getLaunchedFromUid()
*/
@Nullable
public String getLaunchedFromPackage() {
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 52ef7fbb3eca..1b923122d889 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -202,6 +202,12 @@ public class ActivityOptions extends ComponentOptions {
private static final String KEY_LOCK_TASK_MODE = "android:activity.lockTaskMode";
/**
+ * Whether the launching app's identity should be available to the launched activity.
+ * @see #setShareIdentityEnabled(boolean)
+ */
+ private static final String KEY_SHARE_IDENTITY = "android:activity.shareIdentity";
+
+ /**
* The display id the activity should be launched into.
* @see #setLaunchDisplayId(int)
* @hide
@@ -457,6 +463,7 @@ public class ActivityOptions extends ComponentOptions {
private int mLaunchTaskId = -1;
private int mPendingIntentLaunchFlags;
private boolean mLockTaskMode = false;
+ private boolean mShareIdentity = false;
private boolean mDisallowEnterPictureInPictureWhileLaunching;
private boolean mApplyActivityFlagsForBubbles;
private boolean mTaskAlwaysOnTop;
@@ -1238,6 +1245,7 @@ public class ActivityOptions extends ComponentOptions {
break;
}
mLockTaskMode = opts.getBoolean(KEY_LOCK_TASK_MODE, false);
+ mShareIdentity = opts.getBoolean(KEY_SHARE_IDENTITY, false);
mLaunchDisplayId = opts.getInt(KEY_LAUNCH_DISPLAY_ID, INVALID_DISPLAY);
mCallerDisplayId = opts.getInt(KEY_CALLER_DISPLAY_ID, INVALID_DISPLAY);
mLaunchTaskDisplayArea = opts.getParcelable(KEY_LAUNCH_TASK_DISPLAY_AREA_TOKEN, android.window.WindowContainerToken.class);
@@ -1488,6 +1496,20 @@ public class ActivityOptions extends ComponentOptions {
}
/**
+ * Returns whether the launching app has opted-in to sharing its identity with the launched
+ * activity.
+ *
+ * @see #setShareIdentityEnabled(boolean)
+ * @see Activity#getLaunchedFromUid()
+ * @see Activity#getLaunchedFromPackage()
+ *
+ * @hide
+ */
+ public boolean getShareIdentity() {
+ return mShareIdentity;
+ }
+
+ /**
* Gets whether the activity want to be launched as other theme for the splash screen.
* @hide
*/
@@ -1560,6 +1582,33 @@ public class ActivityOptions extends ComponentOptions {
}
/**
+ * Sets whether the identity of the launching app should be shared with the activity.
+ *
+ * <p>Use this option when starting an activity that needs to know the identity of the
+ * launching app; with this set to {@code true}, the activity will have access to the launching
+ * app's package name and uid.
+ *
+ * <p>Defaults to {@code false} if not set.
+ *
+ * <p>Note, even if the launching app does not explicitly enable sharing of its identity, if
+ * the activity is started with {@code Activity#startActivityForResult}, then {@link
+ * Activity#getCallingPackage()} will still return the launching app's package name to
+ * allow validation of the result's recipient. Also, an activity running within a package
+ * signed by the same key used to sign the platform (some system apps such as Settings will
+ * be signed with the platform's key) will have access to the launching app's identity.
+ *
+ * @param shareIdentity whether the launching app's identity should be shared with the activity
+ * @return {@code this} {@link ActivityOptions} instance.
+ * @see Activity#getLaunchedFromPackage()
+ * @see Activity#getLaunchedFromUid()
+ */
+ @NonNull
+ public ActivityOptions setShareIdentityEnabled(boolean shareIdentity) {
+ mShareIdentity = shareIdentity;
+ return this;
+ }
+
+ /**
* Gets the id of the display where activity should be launched.
* @return The id of the display where activity should be launched,
* {@link android.view.Display#INVALID_DISPLAY} if not set.
@@ -2039,6 +2088,7 @@ public class ActivityOptions extends ComponentOptions {
break;
}
mLockTaskMode = otherOptions.mLockTaskMode;
+ mShareIdentity = otherOptions.mShareIdentity;
mAnimSpecs = otherOptions.mAnimSpecs;
mAnimationFinishedListener = otherOptions.mAnimationFinishedListener;
mSpecsFuture = otherOptions.mSpecsFuture;
@@ -2123,6 +2173,9 @@ public class ActivityOptions extends ComponentOptions {
if (mLockTaskMode) {
b.putBoolean(KEY_LOCK_TASK_MODE, mLockTaskMode);
}
+ if (mShareIdentity) {
+ b.putBoolean(KEY_SHARE_IDENTITY, mShareIdentity);
+ }
if (mLaunchDisplayId != INVALID_DISPLAY) {
b.putInt(KEY_LAUNCH_DISPLAY_ID, mLaunchDisplayId);
}
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 569f4dd8328c..4d3f9e457ae3 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -3816,8 +3816,17 @@ public class ApplicationPackageManager extends PackageManager {
@NonNull String targetPackageName) throws NameNotFoundException {
Objects.requireNonNull(sourcePackageName);
Objects.requireNonNull(targetPackageName);
+ return canPackageQuery(sourcePackageName, new String[]{targetPackageName})[0];
+ }
+
+ @Override
+ @NonNull
+ public boolean[] canPackageQuery(@NonNull String sourcePackageName,
+ @NonNull String[] targetPackageNames) throws NameNotFoundException {
+ Objects.requireNonNull(sourcePackageName);
+ Objects.requireNonNull(targetPackageNames);
try {
- return mPM.canPackageQuery(sourcePackageName, targetPackageName, getUserId());
+ return mPM.canPackageQuery(sourcePackageName, targetPackageNames, getUserId());
} catch (ParcelableException e) {
e.maybeRethrow(PackageManager.NameNotFoundException.class);
throw new RuntimeException(e);
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index f20503cef705..91add2783c0d 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -249,6 +249,11 @@ interface IActivityTaskManager {
/** Returns an interface enabling the management of window organizers. */
IWindowOrganizerController getWindowOrganizerController();
+ /**
+ * Sets whether we are currently in an interactive split screen resize operation where we
+ * are changing the docked stack size.
+ */
+ void setSplitScreenResizing(boolean resizing);
boolean supportsLocalVoiceInteraction();
// Get device configuration
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index f63f406f847d..6c430105e2f1 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -511,10 +511,26 @@ public class StatusBarManager {
@SystemApi
public static final int MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER = 1;
+ /**
+ * State indicating that media transfer to this receiver device is succeeded.
+ *
+ * @hide
+ */
+ public static final int MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED = 2;
+
+ /**
+ * State indicating that media transfer to this receiver device is failed.
+ *
+ * @hide
+ */
+ public static final int MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_FAILED = 3;
+
/** @hide */
@IntDef(prefix = {"MEDIA_TRANSFER_RECEIVER_STATE_"}, value = {
MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER,
+ MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+ MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_FAILED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface MediaTransferReceiverState {}
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 5c47ea2aa3f0..f17d18652e98 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -33,6 +33,7 @@ import android.hardware.input.VirtualMouseRelativeEvent;
import android.hardware.input.VirtualMouseScrollEvent;
import android.hardware.input.VirtualTouchEvent;
import android.hardware.input.VirtualTouchscreenConfig;
+import android.hardware.input.VirtualNavigationTouchpadConfig;
import android.os.ResultReceiver;
/**
@@ -84,6 +85,10 @@ interface IVirtualDevice {
void createVirtualTouchscreen(
in VirtualTouchscreenConfig config,
IBinder token);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+ void createVirtualNavigationTouchpad(
+ in VirtualNavigationTouchpadConfig config,
+ IBinder token);
void unregisterInputDevice(IBinder token);
int getInputDeviceId(IBinder token);
boolean sendDpadKeyEvent(IBinder token, in VirtualKeyEvent event);
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 57bdf2d5212c..dba7c8e630c8 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -49,6 +49,8 @@ import android.hardware.input.VirtualKeyboard;
import android.hardware.input.VirtualKeyboardConfig;
import android.hardware.input.VirtualMouse;
import android.hardware.input.VirtualMouseConfig;
+import android.hardware.input.VirtualNavigationTouchpad;
+import android.hardware.input.VirtualNavigationTouchpadConfig;
import android.hardware.input.VirtualTouchscreen;
import android.hardware.input.VirtualTouchscreenConfig;
import android.os.Binder;
@@ -660,6 +662,30 @@ public final class VirtualDeviceManager {
}
/**
+ * Creates a virtual touchpad in navigation mode.
+ *
+ * A touchpad in navigation mode means that its events are interpreted as navigation events
+ * (up, down, etc) instead of using them to update a cursor's absolute position. If the
+ * events are not consumed they are converted to DPAD events.
+ *
+ * @param config the configurations of the virtual navigation touchpad.
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ @NonNull
+ public VirtualNavigationTouchpad createVirtualNavigationTouchpad(
+ @NonNull VirtualNavigationTouchpadConfig config) {
+ try {
+ final IBinder token = new Binder(
+ "android.hardware.input.VirtualNavigationTouchpad:"
+ + config.getInputDeviceName());
+ mVirtualDevice.createVirtualNavigationTouchpad(config, token);
+ return new VirtualNavigationTouchpad(mVirtualDevice, token);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Creates a virtual touchscreen.
*
* @param display the display that the events inputted through this device should
@@ -776,11 +802,11 @@ public final class VirtualDeviceManager {
private String getVirtualDisplayName() {
try {
- // Currently this just use the association ID, which means all of the virtual
- // displays created using the same virtual device will have the same name. The name
- // should only be used for informational purposes, and not for identifying the
- // display in code.
- return "VirtualDevice_" + mVirtualDevice.getAssociationId();
+ // Currently this just use the device ID, which means all of the virtual displays
+ // created using the same virtual device will have the same name. The name should
+ // only be used for informational purposes, and not for identifying the display in
+ // code.
+ return "VirtualDevice_" + mVirtualDevice.getDeviceId();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 35afe9f727db..81bea2e3573f 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -797,5 +797,5 @@ interface IPackageManager {
void setKeepUninstalledPackages(in List<String> packageList);
- boolean canPackageQuery(String sourcePackageName, String targetPackageName, int userId);
+ boolean[] canPackageQuery(String sourcePackageName, in String[] targetPackageNames, int userId);
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index ec490d10d45e..8ae53100d4d5 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -10388,6 +10388,30 @@ public abstract class PackageManager {
}
/**
+ * Same as {@link #canPackageQuery(String, String)} but accepts an array of target packages to
+ * be queried.
+ *
+ * @param sourcePackageName The source package that would receive details about the
+ * target package.
+ * @param targetPackageNames An array of target packages whose details would be shared with the
+ * source package.
+ * @return An array of booleans where each member specifies whether the source package is able
+ * to query for details about the target package given by the corresponding value at the same
+ * index in the array of target packages.
+ * @throws NameNotFoundException if either a given package can not be found on the
+ * system, or if the caller is not able to query for details about the source or
+ * target packages.
+ * @hide
+ */
+ @SystemApi
+ @NonNull
+ public boolean[] canPackageQuery(@NonNull String sourcePackageName,
+ @NonNull String[] targetPackageNames) throws NameNotFoundException {
+ throw new UnsupportedOperationException(
+ "canPackageQuery not implemented in subclass");
+ }
+
+ /**
* Makes a package that provides an authority {@code visibleAuthority} become visible to the
* application {@code recipientUid}.
*
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 829908fc11d6..7409187cfb49 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -439,9 +439,6 @@ public abstract class DisplayManagerInternal {
// 1 (brighter). Set to Float.NaN if there's no override.
public float screenAutoBrightnessAdjustmentOverride;
- // If true, enables automatic brightness control.
- public boolean useAutoBrightness;
-
// If true, scales the brightness to a fraction of desired (as defined by
// screenLowPowerBrightnessFactor).
public boolean lowPowerMode;
@@ -471,7 +468,6 @@ public abstract class DisplayManagerInternal {
policy = POLICY_BRIGHT;
useProximitySensor = false;
screenBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT;
- useAutoBrightness = false;
screenAutoBrightnessAdjustmentOverride = Float.NaN;
screenLowPowerBrightnessFactor = 0.5f;
blockScreenOn = false;
@@ -491,7 +487,6 @@ public abstract class DisplayManagerInternal {
policy = other.policy;
useProximitySensor = other.useProximitySensor;
screenBrightnessOverride = other.screenBrightnessOverride;
- useAutoBrightness = other.useAutoBrightness;
screenAutoBrightnessAdjustmentOverride = other.screenAutoBrightnessAdjustmentOverride;
screenLowPowerBrightnessFactor = other.screenLowPowerBrightnessFactor;
blockScreenOn = other.blockScreenOn;
@@ -513,7 +508,6 @@ public abstract class DisplayManagerInternal {
&& useProximitySensor == other.useProximitySensor
&& floatEquals(screenBrightnessOverride,
other.screenBrightnessOverride)
- && useAutoBrightness == other.useAutoBrightness
&& floatEquals(screenAutoBrightnessAdjustmentOverride,
other.screenAutoBrightnessAdjustmentOverride)
&& screenLowPowerBrightnessFactor
@@ -539,7 +533,6 @@ public abstract class DisplayManagerInternal {
return "policy=" + policyToString(policy)
+ ", useProximitySensor=" + useProximitySensor
+ ", screenBrightnessOverride=" + screenBrightnessOverride
- + ", useAutoBrightness=" + useAutoBrightness
+ ", screenAutoBrightnessAdjustmentOverride="
+ screenAutoBrightnessAdjustmentOverride
+ ", screenLowPowerBrightnessFactor=" + screenLowPowerBrightnessFactor
diff --git a/core/java/android/hardware/input/VirtualNavigationTouchpad.java b/core/java/android/hardware/input/VirtualNavigationTouchpad.java
new file mode 100644
index 000000000000..2854034cd127
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualNavigationTouchpad.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.companion.virtual.IVirtualDevice;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+/**
+ * A virtual navigation touchpad representing a touch-based input mechanism on a remote device.
+ *
+ * <p>This registers an InputDevice that is interpreted like a physically-connected device and
+ * dispatches received events to it.
+ *
+ * <p>The virtual touchpad will be in navigation mode. Motion results in focus traversal in the same
+ * manner as D-Pad navigation if the events are not consumed.
+ *
+ * @see android.view.InputDevice#SOURCE_TOUCH_NAVIGATION
+ *
+ * @hide
+ */
+@SystemApi
+public class VirtualNavigationTouchpad extends VirtualInputDevice {
+
+ /** @hide */
+ public VirtualNavigationTouchpad(IVirtualDevice virtualDevice, IBinder token) {
+ super(virtualDevice, token);
+ }
+
+ /**
+ * Sends a touch event to the system.
+ *
+ * @param event the event to send
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void sendTouchEvent(@NonNull VirtualTouchEvent event) {
+ try {
+ mVirtualDevice.sendTouchEvent(mToken, event);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/core/java/android/hardware/input/VirtualNavigationTouchpadConfig.aidl b/core/java/android/hardware/input/VirtualNavigationTouchpadConfig.aidl
new file mode 100644
index 000000000000..d9124910adff
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualNavigationTouchpadConfig.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+parcelable VirtualNavigationTouchpadConfig;
diff --git a/core/java/android/hardware/input/VirtualNavigationTouchpadConfig.java b/core/java/android/hardware/input/VirtualNavigationTouchpadConfig.java
new file mode 100644
index 000000000000..f2805bb1029e
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualNavigationTouchpadConfig.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Configurations to create virtual navigation touchpad.
+ *
+ * @hide
+ */
+@SystemApi
+public final class VirtualNavigationTouchpadConfig extends VirtualInputDeviceConfig
+ implements Parcelable {
+
+ /** The touchpad height. */
+ private final int mHeight;
+ /** The touchpad width. */
+ private final int mWidth;
+
+ private VirtualNavigationTouchpadConfig(@NonNull Builder builder) {
+ super(builder);
+ mHeight = builder.mHeight;
+ mWidth = builder.mWidth;
+ }
+
+ private VirtualNavigationTouchpadConfig(@NonNull Parcel in) {
+ super(in);
+ mHeight = in.readInt();
+ mWidth = in.readInt();
+ }
+
+ /** Returns the touchpad height. */
+ public int getHeight() {
+ return mHeight;
+ }
+
+ /** Returns the touchpad width. */
+ public int getWidth() {
+ return mWidth;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(mHeight);
+ dest.writeInt(mWidth);
+ }
+
+ @NonNull
+ public static final Creator<VirtualNavigationTouchpadConfig> CREATOR =
+ new Creator<VirtualNavigationTouchpadConfig>() {
+ @Override
+ public VirtualNavigationTouchpadConfig createFromParcel(Parcel in) {
+ return new VirtualNavigationTouchpadConfig(in);
+ }
+
+ @Override
+ public VirtualNavigationTouchpadConfig[] newArray(int size) {
+ return new VirtualNavigationTouchpadConfig[size];
+ }
+ };
+
+ /**
+ * Builder for creating a {@link VirtualNavigationTouchpadConfig}.
+ */
+ public static final class Builder extends VirtualInputDeviceConfig.Builder<Builder> {
+
+ private final int mHeight;
+ private final int mWidth;
+
+ public Builder(@IntRange(from = 1) int touchpadHeight,
+ @IntRange(from = 1) int touchpadWidth) {
+ if (touchpadHeight <= 0 || touchpadWidth <= 0) {
+ throw new IllegalArgumentException(
+ "Cannot create a virtual navigation touchpad, touchpad dimensions must be "
+ + "positive. Got: (" + touchpadHeight + ", "
+ + touchpadWidth + ")");
+ }
+ mHeight = touchpadHeight;
+ mWidth = touchpadWidth;
+ }
+
+ /**
+ * Builds the {@link VirtualNavigationTouchpadConfig} instance.
+ */
+ @NonNull
+ public VirtualNavigationTouchpadConfig build() {
+ return new VirtualNavigationTouchpadConfig(this);
+ }
+ }
+}
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
index 2339656979b5..b8850f427cfc 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -42,6 +42,7 @@ import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -130,6 +131,30 @@ public final class VcnGatewayConnectionConfig {
})
public @interface VcnSupportedCapability {}
+ /**
+ * Perform mobility update to attempt recovery from suspected data stalls.
+ *
+ * <p>If set, the gatway connection will monitor the data stall detection of the VCN network.
+ * When there is a suspected data stall, the gateway connection will attempt recovery by
+ * performing a mobility update on the underlying IKE session.
+ */
+ public static final int VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY = 0;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = {"VCN_GATEWAY_OPTION_"},
+ value = {
+ VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY,
+ })
+ public @interface VcnGatewayOption {}
+
+ private static final Set<Integer> ALLOWED_GATEWAY_OPTIONS = new ArraySet<>();
+
+ static {
+ ALLOWED_GATEWAY_OPTIONS.add(VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY);
+ }
+
private static final int DEFAULT_MAX_MTU = 1500;
/**
@@ -201,6 +226,9 @@ public final class VcnGatewayConnectionConfig {
private static final String RETRY_INTERVAL_MS_KEY = "mRetryIntervalsMs";
@NonNull private final long[] mRetryIntervalsMs;
+ private static final String GATEWAY_OPTIONS_KEY = "mGatewayOptions";
+ @NonNull private final Set<Integer> mGatewayOptions;
+
/** Builds a VcnGatewayConnectionConfig with the specified parameters. */
private VcnGatewayConnectionConfig(
@NonNull String gatewayConnectionName,
@@ -208,12 +236,14 @@ public final class VcnGatewayConnectionConfig {
@NonNull Set<Integer> exposedCapabilities,
@NonNull List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates,
@NonNull long[] retryIntervalsMs,
- @IntRange(from = MIN_MTU_V6) int maxMtu) {
+ @IntRange(from = MIN_MTU_V6) int maxMtu,
+ @NonNull Set<Integer> gatewayOptions) {
mGatewayConnectionName = gatewayConnectionName;
mTunnelConnectionParams = tunnelConnectionParams;
mExposedCapabilities = new TreeSet(exposedCapabilities);
mRetryIntervalsMs = retryIntervalsMs;
mMaxMtu = maxMtu;
+ mGatewayOptions = Collections.unmodifiableSet(new HashSet(gatewayOptions));
mUnderlyingNetworkTemplates = new ArrayList<>(underlyingNetworkTemplates);
if (mUnderlyingNetworkTemplates.isEmpty()) {
@@ -256,6 +286,20 @@ public final class VcnGatewayConnectionConfig {
VcnUnderlyingNetworkTemplate::fromPersistableBundle);
}
+ final PersistableBundle gatewayOptionsBundle = in.getPersistableBundle(GATEWAY_OPTIONS_KEY);
+
+ if (gatewayOptionsBundle == null) {
+ // GATEWAY_OPTIONS_KEY was added in Android U. Thus VcnGatewayConnectionConfig created
+ // on old platforms will not have this data and will be assigned with the default value
+ mGatewayOptions = Collections.emptySet();
+ } else {
+ mGatewayOptions =
+ new HashSet<>(
+ PersistableBundleUtils.toList(
+ gatewayOptionsBundle,
+ PersistableBundleUtils.INTEGER_DESERIALIZER));
+ }
+
mRetryIntervalsMs = in.getLongArray(RETRY_INTERVAL_MS_KEY);
mMaxMtu = in.getInt(MAX_MTU_KEY);
@@ -279,6 +323,10 @@ public final class VcnGatewayConnectionConfig {
Preconditions.checkArgument(
mMaxMtu >= MIN_MTU_V6, "maxMtu must be at least IPv6 min MTU (1280)");
+
+ for (int option : mGatewayOptions) {
+ validateGatewayOption(option);
+ }
}
private static void checkValidCapability(int capability) {
@@ -315,6 +363,12 @@ public final class VcnGatewayConnectionConfig {
}
}
+ private static void validateGatewayOption(int option) {
+ if (!ALLOWED_GATEWAY_OPTIONS.contains(option)) {
+ throw new IllegalArgumentException("Invalid vcn gateway option: " + option);
+ }
+ }
+
/**
* Returns the configured Gateway Connection name.
*
@@ -399,6 +453,19 @@ public final class VcnGatewayConnectionConfig {
}
/**
+ * Checks if the given VCN gateway option is enabled.
+ *
+ * @param option the option to check.
+ * @throws IllegalArgumentException if the provided option is invalid.
+ * @see Builder#addGatewayOption(int)
+ * @see Builder#removeGatewayOption(int)
+ */
+ public boolean hasGatewayOption(@VcnGatewayOption int option) {
+ validateGatewayOption(option);
+ return mGatewayOptions.contains(option);
+ }
+
+ /**
* Converts this config to a PersistableBundle.
*
* @hide
@@ -418,11 +485,16 @@ public final class VcnGatewayConnectionConfig {
PersistableBundleUtils.fromList(
mUnderlyingNetworkTemplates,
VcnUnderlyingNetworkTemplate::toPersistableBundle);
+ final PersistableBundle gatewayOptionsBundle =
+ PersistableBundleUtils.fromList(
+ new ArrayList<>(mGatewayOptions),
+ PersistableBundleUtils.INTEGER_SERIALIZER);
result.putString(GATEWAY_CONNECTION_NAME_KEY, mGatewayConnectionName);
result.putPersistableBundle(TUNNEL_CONNECTION_PARAMS_KEY, tunnelConnectionParamsBundle);
result.putPersistableBundle(EXPOSED_CAPABILITIES_KEY, exposedCapsBundle);
result.putPersistableBundle(UNDERLYING_NETWORK_TEMPLATES_KEY, networkTemplatesBundle);
+ result.putPersistableBundle(GATEWAY_OPTIONS_KEY, gatewayOptionsBundle);
result.putLongArray(RETRY_INTERVAL_MS_KEY, mRetryIntervalsMs);
result.putInt(MAX_MTU_KEY, mMaxMtu);
@@ -437,7 +509,8 @@ public final class VcnGatewayConnectionConfig {
mExposedCapabilities,
mUnderlyingNetworkTemplates,
Arrays.hashCode(mRetryIntervalsMs),
- mMaxMtu);
+ mMaxMtu,
+ mGatewayOptions);
}
@Override
@@ -452,7 +525,8 @@ public final class VcnGatewayConnectionConfig {
&& mExposedCapabilities.equals(rhs.mExposedCapabilities)
&& mUnderlyingNetworkTemplates.equals(rhs.mUnderlyingNetworkTemplates)
&& Arrays.equals(mRetryIntervalsMs, rhs.mRetryIntervalsMs)
- && mMaxMtu == rhs.mMaxMtu;
+ && mMaxMtu == rhs.mMaxMtu
+ && mGatewayOptions.equals(rhs.mGatewayOptions);
}
/**
@@ -470,6 +544,8 @@ public final class VcnGatewayConnectionConfig {
@NonNull private long[] mRetryIntervalsMs = DEFAULT_RETRY_INTERVALS_MS;
private int mMaxMtu = DEFAULT_MAX_MTU;
+ @NonNull private final Set<Integer> mGatewayOptions = new ArraySet<>();
+
// TODO: (b/175829816) Consider VCN-exposed capabilities that may be transport dependent.
// Consider the case where the VCN might only expose MMS on WiFi, but defer to MMS
// when on Cell.
@@ -628,6 +704,34 @@ public final class VcnGatewayConnectionConfig {
}
/**
+ * Enables the specified VCN gateway option.
+ *
+ * @param option the option to be enabled
+ * @return this {@link Builder} instance, for chaining
+ * @throws IllegalArgumentException if the provided option is invalid
+ */
+ @NonNull
+ public Builder addGatewayOption(@VcnGatewayOption int option) {
+ validateGatewayOption(option);
+ mGatewayOptions.add(option);
+ return this;
+ }
+
+ /**
+ * Resets (disables) the specified VCN gateway option.
+ *
+ * @param option the option to be disabled
+ * @return this {@link Builder} instance, for chaining
+ * @throws IllegalArgumentException if the provided option is invalid
+ */
+ @NonNull
+ public Builder removeGatewayOption(@VcnGatewayOption int option) {
+ validateGatewayOption(option);
+ mGatewayOptions.remove(option);
+ return this;
+ }
+
+ /**
* Builds and validates the VcnGatewayConnectionConfig.
*
* @return an immutable VcnGatewayConnectionConfig instance
@@ -640,7 +744,8 @@ public final class VcnGatewayConnectionConfig {
mExposedCapabilities,
mUnderlyingNetworkTemplates,
mRetryIntervalsMs,
- mMaxMtu);
+ mMaxMtu,
+ mGatewayOptions);
}
}
}
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index a529ac6569bd..712d328e9dc9 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -177,12 +177,15 @@ public final class Looper {
final long traceTag = me.mTraceTag;
long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
- if (thresholdOverride > 0) {
+
+ final boolean hasOverride = thresholdOverride >= 0;
+ if (hasOverride) {
slowDispatchThresholdMs = thresholdOverride;
slowDeliveryThresholdMs = thresholdOverride;
}
- final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
- final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);
+ final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0 || hasOverride)
+ && (msg.when > 0);
+ final boolean logSlowDispatch = (slowDispatchThresholdMs > 0 || hasOverride);
final boolean needStartTime = logSlowDelivery || logSlowDispatch;
final boolean needEndTime = logSlowDispatch;
@@ -283,7 +286,7 @@ public final class Looper {
SystemProperties.getInt("log.looper."
+ Process.myUid() + "."
+ Thread.currentThread().getName()
- + ".slow", 0);
+ + ".slow", -1);
me.mSlowDeliveryDetected = false;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 90aca14cf860..25f15d8171de 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10723,6 +10723,49 @@ public final class Settings {
"back_gesture_inset_scale_right";
/**
+ * Indicates whether the trackpad back gesture is enabled.
+ * <p>Type: int (0 for false, 1 for true)
+ *
+ * @hide
+ */
+ public static final String TRACKPAD_GESTURE_BACK_ENABLED = "trackpad_gesture_back_enabled";
+
+ /**
+ * Indicates whether the trackpad home gesture is enabled.
+ * <p>Type: int (0 for false, 1 for true)
+ *
+ * @hide
+ */
+ public static final String TRACKPAD_GESTURE_HOME_ENABLED = "trackpad_gesture_home_enabled";
+
+ /**
+ * Indicates whether the trackpad overview gesture is enabled.
+ * <p>Type: int (0 for false, 1 for true)
+ *
+ * @hide
+ */
+ public static final String TRACKPAD_GESTURE_OVERVIEW_ENABLED =
+ "trackpad_gesture_overview_enabled";
+
+ /**
+ * Indicates whether the trackpad notification gesture is enabled.
+ * <p>Type: int (0 for false, 1 for true)
+ *
+ * @hide
+ */
+ public static final String TRACKPAD_GESTURE_NOTIFICATION_ENABLED =
+ "trackpad_gesture_notification_enabled";
+
+ /**
+ * Indicates whether the trackpad quick switch gesture is enabled.
+ * <p>Type: int (0 for false, 1 for true)
+ *
+ * @hide
+ */
+ public static final String TRACKPAD_GESTURE_QUICK_SWITCH_ENABLED =
+ "trackpad_gesture_quick_switch_enabled";
+
+ /**
* Current provider of proximity-based sharing services.
* Default value in @string/config_defaultNearbySharingComponent.
* No VALIDATOR as this setting will not be backed up.
diff --git a/core/java/android/service/voice/AbstractHotwordDetector.java b/core/java/android/service/voice/AbstractHotwordDetector.java
index 28357efeafd0..c90ab6777515 100644
--- a/core/java/android/service/voice/AbstractHotwordDetector.java
+++ b/core/java/android/service/voice/AbstractHotwordDetector.java
@@ -25,7 +25,9 @@ import android.app.ActivityThread;
import android.app.compat.CompatChanges;
import android.media.AudioFormat;
import android.media.permission.Identity;
+import android.os.Binder;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
@@ -49,19 +51,20 @@ abstract class AbstractHotwordDetector implements HotwordDetector {
private final IVoiceInteractionManagerService mManagerService;
private final Handler mHandler;
private final HotwordDetector.Callback mCallback;
- private final int mDetectorType;
private Consumer<AbstractHotwordDetector> mOnDestroyListener;
private final AtomicBoolean mIsDetectorActive;
+ /**
+ * A token which is used by voice interaction system service to identify different detectors.
+ */
+ private final IBinder mToken = new Binder();
AbstractHotwordDetector(
IVoiceInteractionManagerService managerService,
- HotwordDetector.Callback callback,
- int detectorType) {
+ HotwordDetector.Callback callback) {
mManagerService = managerService;
// TODO: this needs to be supplied from above
mHandler = new Handler(Looper.getMainLooper());
mCallback = callback;
- mDetectorType = detectorType;
mIsDetectorActive = new AtomicBoolean(true);
}
@@ -94,6 +97,7 @@ abstract class AbstractHotwordDetector implements HotwordDetector {
audioStream,
audioFormat,
options,
+ mToken,
new BinderCallback(mHandler, mCallback));
} catch (RemoteException e) {
e.rethrowFromSystemServer();
@@ -111,7 +115,7 @@ abstract class AbstractHotwordDetector implements HotwordDetector {
}
throwIfDetectorIsNoLongerActive();
try {
- mManagerService.updateState(options, sharedMemory);
+ mManagerService.updateState(options, sharedMemory, mToken);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -128,7 +132,7 @@ abstract class AbstractHotwordDetector implements HotwordDetector {
Identity identity = new Identity();
identity.packageName = ActivityThread.currentOpPackageName();
try {
- mManagerService.initAndVerifyDetector(identity, options, sharedMemory, callback,
+ mManagerService.initAndVerifyDetector(identity, options, sharedMemory, mToken, callback,
detectorType);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -151,6 +155,11 @@ abstract class AbstractHotwordDetector implements HotwordDetector {
return;
}
mIsDetectorActive.set(false);
+ try {
+ mManagerService.destroyDetector(mToken);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
synchronized (mLock) {
mOnDestroyListener.accept(this);
}
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 320280b9e68b..9008bf7d48ec 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -293,7 +293,7 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
private final Handler mHandler;
private final IBinder mBinder = new Binder();
private final int mTargetSdkVersion;
- private final boolean mSupportHotwordDetectionService;
+ private final boolean mSupportSandboxedDetectionService;
@GuardedBy("mLock")
private boolean mIsAvailabilityOverriddenByTestApi = false;
@@ -756,7 +756,7 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
* @param callback A non-null Callback for receiving the recognition events.
* @param modelManagementService A service that allows management of sound models.
* @param targetSdkVersion The target SDK version.
- * @param supportHotwordDetectionService {@code true} if HotwordDetectionService should be
+ * @param SupportSandboxedDetectionService {@code true} if HotwordDetectionService should be
* triggered, otherwise {@code false}.
*
* @hide
@@ -764,10 +764,8 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
public AlwaysOnHotwordDetector(String text, Locale locale, Callback callback,
KeyphraseEnrollmentInfo keyphraseEnrollmentInfo,
IVoiceInteractionManagerService modelManagementService, int targetSdkVersion,
- boolean supportHotwordDetectionService) {
- super(modelManagementService, callback,
- supportHotwordDetectionService ? DETECTOR_TYPE_TRUSTED_HOTWORD_DSP
- : DETECTOR_TYPE_NORMAL);
+ boolean supportSandboxedDetectionService) {
+ super(modelManagementService, callback);
mHandler = new MyHandler();
mText = text;
@@ -777,12 +775,12 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
mInternalCallback = new SoundTriggerListener(mHandler);
mModelManagementService = modelManagementService;
mTargetSdkVersion = targetSdkVersion;
- mSupportHotwordDetectionService = supportHotwordDetectionService;
+ mSupportSandboxedDetectionService = supportSandboxedDetectionService;
}
@Override
void initialize(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) {
- if (mSupportHotwordDetectionService) {
+ if (mSupportSandboxedDetectionService) {
initAndVerifyDetector(options, sharedMemory, mInternalCallback,
DETECTOR_TYPE_TRUSTED_HOTWORD_DSP);
}
@@ -814,7 +812,7 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
public final void updateState(@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory) throws IllegalDetectorStateException {
synchronized (mLock) {
- if (!mSupportHotwordDetectionService) {
+ if (!mSupportSandboxedDetectionService) {
throw new IllegalStateException(
"updateState called, but it doesn't support hotword detection service");
}
@@ -1410,8 +1408,8 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
* @hide
*/
@Override
- public boolean isUsingHotwordDetectionService() {
- return mSupportHotwordDetectionService;
+ public boolean isUsingSandboxedDetectionService() {
+ return mSupportSandboxedDetectionService;
}
/**
diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java
index 552a793b6350..a47c09662a51 100644
--- a/core/java/android/service/voice/HotwordDetectionService.java
+++ b/core/java/android/service/voice/HotwordDetectionService.java
@@ -140,7 +140,7 @@ public abstract class HotwordDetectionService extends Service {
@Nullable
private IRecognitionServiceManager mIRecognitionServiceManager;
- private final IHotwordDetectionService mInterface = new IHotwordDetectionService.Stub() {
+ private final ISandboxedDetectionService mInterface = new ISandboxedDetectionService.Stub() {
@Override
public void detectFromDspSource(
SoundTrigger.KeyphraseRecognitionEvent event,
diff --git a/core/java/android/service/voice/HotwordDetector.java b/core/java/android/service/voice/HotwordDetector.java
index 7112dc666509..b7f7d54fc055 100644
--- a/core/java/android/service/voice/HotwordDetector.java
+++ b/core/java/android/service/voice/HotwordDetector.java
@@ -178,7 +178,7 @@ public interface HotwordDetector {
/**
* @hide
*/
- default boolean isUsingHotwordDetectionService() {
+ default boolean isUsingSandboxedDetectionService() {
throw new UnsupportedOperationException("Not implemented. Must override in a subclass.");
}
diff --git a/core/java/android/service/voice/IHotwordDetectionService.aidl b/core/java/android/service/voice/ISandboxedDetectionService.aidl
index 9ef930707f07..5537fd1df26e 100644
--- a/core/java/android/service/voice/IHotwordDetectionService.aidl
+++ b/core/java/android/service/voice/ISandboxedDetectionService.aidl
@@ -29,11 +29,11 @@ import android.view.contentcapture.IContentCaptureManager;
import android.speech.IRecognitionServiceManager;
/**
- * Provide the interface to communicate with hotword detection service.
+ * Provide the interface to communicate with sandboxed detection service.
*
* @hide
*/
-oneway interface IHotwordDetectionService {
+oneway interface ISandboxedDetectionService {
void detectFromDspSource(
in SoundTrigger.KeyphraseRecognitionEvent event,
in AudioFormat audioFormat,
diff --git a/core/java/android/service/voice/SoftwareHotwordDetector.java b/core/java/android/service/voice/SoftwareHotwordDetector.java
index 11688df2f1a4..f1b774591394 100644
--- a/core/java/android/service/voice/SoftwareHotwordDetector.java
+++ b/core/java/android/service/voice/SoftwareHotwordDetector.java
@@ -59,7 +59,7 @@ class SoftwareHotwordDetector extends AbstractHotwordDetector {
IVoiceInteractionManagerService managerService,
AudioFormat audioFormat,
HotwordDetector.Callback callback) {
- super(managerService, callback, DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE);
+ super(managerService, callback);
mManagerService = managerService;
mAudioFormat = audioFormat;
@@ -129,7 +129,7 @@ class SoftwareHotwordDetector extends AbstractHotwordDetector {
* @hide
*/
@Override
- public boolean isUsingHotwordDetectionService() {
+ public boolean isUsingSandboxedDetectionService() {
return true;
}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 7c125c7f7676..a59578ee8d9d 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -430,7 +430,7 @@ public class VoiceInteractionService extends Service {
safelyShutdownAllHotwordDetectors();
} else {
for (HotwordDetector detector : mActiveHotwordDetectors) {
- if (detector.isUsingHotwordDetectionService()
+ if (detector.isUsingSandboxedDetectionService()
!= supportHotwordDetectionService) {
throw new IllegalStateException(
"It disallows to create trusted and non-trusted detectors "
@@ -513,7 +513,7 @@ public class VoiceInteractionService extends Service {
safelyShutdownAllHotwordDetectors();
} else {
for (HotwordDetector detector : mActiveHotwordDetectors) {
- if (!detector.isUsingHotwordDetectionService()) {
+ if (!detector.isUsingSandboxedDetectionService()) {
throw new IllegalStateException(
"It disallows to create trusted and non-trusted detectors "
+ "at the same time.");
@@ -605,7 +605,7 @@ public class VoiceInteractionService extends Service {
private void shutdownHotwordDetectionServiceIfRequiredLocked() {
for (HotwordDetector detector : mActiveHotwordDetectors) {
- if (detector.isUsingHotwordDetectionService()) {
+ if (detector.isUsingSandboxedDetectionService()) {
return;
}
}
diff --git a/core/java/android/service/voice/VoiceInteractionServiceInfo.java b/core/java/android/service/voice/VoiceInteractionServiceInfo.java
index ff03cc14e73b..af29961c98bc 100644
--- a/core/java/android/service/voice/VoiceInteractionServiceInfo.java
+++ b/core/java/android/service/voice/VoiceInteractionServiceInfo.java
@@ -46,6 +46,7 @@ public class VoiceInteractionServiceInfo {
private String mSessionService;
private String mRecognitionService;
private String mHotwordDetectionService;
+ private String mVisualQueryDetectionService;
private String mSettingsActivity;
private boolean mSupportsAssist;
private boolean mSupportsLaunchFromKeyguard;
@@ -137,6 +138,8 @@ public class VoiceInteractionServiceInfo {
R.styleable.VoiceInteractionService_supportsLocalInteraction, false);
mHotwordDetectionService = array.getString(com.android.internal.R.styleable
.VoiceInteractionService_hotwordDetectionService);
+ mVisualQueryDetectionService = array.getString(com.android.internal.R.styleable
+ .VoiceInteractionService_visualQueryDetectionService);
array.recycle();
if (mSessionService == null) {
mParseError = "No sessionService specified";
@@ -190,4 +193,9 @@ public class VoiceInteractionServiceInfo {
public String getHotwordDetectionService() {
return mHotwordDetectionService;
}
+
+ @Nullable
+ public String getVisualQueryDetectionService() {
+ return mVisualQueryDetectionService;
+ }
}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 9d95d0b705ef..84a233ffd2ad 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -394,7 +394,7 @@ public abstract class WallpaperService extends Service {
public void resized(ClientWindowFrames frames, boolean reportDraw,
MergedConfiguration mergedConfiguration, InsetsState insetsState,
boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId,
- int syncSeqId, boolean dragResizing) {
+ int syncSeqId, int resizeMode) {
Message msg = mCaller.obtainMessageIO(MSG_WINDOW_RESIZED,
reportDraw ? 1 : 0,
mergedConfiguration);
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 2c4f38de2db0..897e23ac90e3 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -196,6 +196,7 @@ public class FeatureFlagUtils {
PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY);
PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_TRACKPAD);
PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE);
+ PERSISTENT_FLAGS.add(SETTINGS_ENABLE_SPA);
}
/**
diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java
index c69298192109..3b082bcfcfb2 100644
--- a/core/java/android/view/AttachedSurfaceControl.java
+++ b/core/java/android/view/AttachedSurfaceControl.java
@@ -140,13 +140,13 @@ public interface AttachedSurfaceControl {
}
/**
- * Returns a SyncTarget that can be used to sync {@link AttachedSurfaceControl} in a
+ * Returns a SurfaceSyncGroup that can be used to sync {@link AttachedSurfaceControl} in a
* {@link SurfaceSyncGroup}
*
* @hide
*/
@Nullable
- default SurfaceSyncGroup.SyncTarget getSyncTarget() {
+ default SurfaceSyncGroup getOrCreateSurfaceSyncGroup() {
return null;
}
}
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index d554514349c3..8e16f24b154f 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -57,7 +57,7 @@ oneway interface IWindow {
void resized(in ClientWindowFrames frames, boolean reportDraw,
in MergedConfiguration newMergedConfiguration, in InsetsState insetsState,
boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId,
- int syncSeqId, boolean dragResizing);
+ int syncSeqId, int resizeMode);
/**
* Called when this window retrieved control over a specified set of insets sources.
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 0aba80db5378..6d9f99f76958 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -457,6 +457,12 @@ interface IWindowManager
int getDockedStackSide();
/**
+ * Sets the region the user can touch the divider. This region will be excluded from the region
+ * which is used to cause a focus switch when dispatching touch.
+ */
+ void setDockedTaskDividerTouchRegion(in Rect touchableRegion);
+
+ /**
* Registers a listener that will be called when the pinned task state changes.
*/
void registerPinnedTaskListener(int displayId, IPinnedTaskListener listener);
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index c8a5d8d887f9..4fbb249c507f 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -2199,6 +2199,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
}
/** @hide */
+ @TestApi
@Override
public int getDisplayId() {
return nativeGetDisplayId(mNativePtr);
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 18897725e98f..0a134be9ca84 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -404,14 +404,6 @@ public class SurfaceControlViewHost {
}
/**
- * @hide
- */
- @TestApi
- public void relayout(WindowManager.LayoutParams attrs) {
- relayout(attrs, SurfaceControl.Transaction::apply);
- }
-
- /**
* Forces relayout and draw and allows to set a custom callback when it is finished
* @hide
*/
@@ -423,6 +415,14 @@ public class SurfaceControlViewHost {
}
/**
+ * @hide
+ */
+ @TestApi
+ public void relayout(WindowManager.LayoutParams attrs) {
+ mViewRoot.setLayoutParams(attrs, false);
+ }
+
+ /**
* Modify the size of the root view.
*
* @param width Width in pixels
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 33ea92de68b4..9db084e01598 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -982,8 +982,8 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
final boolean redrawNeeded = sizeChanged || creating || hintChanged
|| (mVisible && !mDrawFinished) || alphaChanged || relativeZChanged;
- boolean shouldSyncBuffer =
- redrawNeeded && viewRoot.wasRelayoutRequested() && viewRoot.isInLocalSync();
+ boolean shouldSyncBuffer = redrawNeeded && viewRoot.wasRelayoutRequested()
+ && viewRoot.isInWMSRequestedSync();
SyncBufferTransactionCallback syncBufferTransactionCallback = null;
if (shouldSyncBuffer) {
syncBufferTransactionCallback = new SyncBufferTransactionCallback();
@@ -1073,35 +1073,34 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
private void handleSyncBufferCallback(SurfaceHolder.Callback[] callbacks,
SyncBufferTransactionCallback syncBufferTransactionCallback) {
- getViewRootImpl().addToSync((parentSyncGroup, syncBufferCallback) ->
- redrawNeededAsync(callbacks, () -> {
- Transaction t = null;
- if (mBlastBufferQueue != null) {
- mBlastBufferQueue.stopContinuousSyncTransaction();
- t = syncBufferTransactionCallback.waitForTransaction();
- }
+ final SurfaceSyncGroup surfaceSyncGroup = new SurfaceSyncGroup();
+ getViewRootImpl().addToSync(surfaceSyncGroup);
+ redrawNeededAsync(callbacks, () -> {
+ Transaction t = null;
+ if (mBlastBufferQueue != null) {
+ mBlastBufferQueue.stopContinuousSyncTransaction();
+ t = syncBufferTransactionCallback.waitForTransaction();
+ }
- syncBufferCallback.onTransactionReady(t);
- onDrawFinished();
- }));
+ surfaceSyncGroup.onTransactionReady(t);
+ onDrawFinished();
+ });
}
private void handleSyncNoBuffer(SurfaceHolder.Callback[] callbacks) {
- final SurfaceSyncGroup syncGroup = new SurfaceSyncGroup();
+ final SurfaceSyncGroup surfaceSyncGroup = new SurfaceSyncGroup();
synchronized (mSyncGroups) {
- mSyncGroups.add(syncGroup);
+ mSyncGroups.add(surfaceSyncGroup);
}
- syncGroup.addToSync((parentSyncGroup, syncBufferCallback) ->
- redrawNeededAsync(callbacks, () -> {
- syncBufferCallback.onTransactionReady(null);
- onDrawFinished();
- synchronized (mSyncGroups) {
- mSyncGroups.remove(syncGroup);
- }
- }));
+ redrawNeededAsync(callbacks, () -> {
+ synchronized (mSyncGroups) {
+ mSyncGroups.remove(surfaceSyncGroup);
+ }
+ surfaceSyncGroup.onTransactionReady(null);
+ onDrawFinished();
+ });
- syncGroup.markSyncReady();
}
private void redrawNeededAsync(SurfaceHolder.Callback[] callbacks,
@@ -1119,7 +1118,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
if (viewRoot != null) {
synchronized (mSyncGroups) {
for (SurfaceSyncGroup syncGroup : mSyncGroups) {
- viewRoot.mergeSync(syncGroup);
+ viewRoot.addToSync(syncGroup);
}
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index eaa6820c6864..b2973eff01e1 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -51,6 +51,8 @@ import static android.view.ViewRootImplProto.WIDTH;
import static android.view.ViewRootImplProto.WINDOW_ATTRIBUTES;
import static android.view.ViewRootImplProto.WIN_FRAME;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
+import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM;
+import static android.view.WindowCallbacks.RESIZE_MODE_INVALID;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
@@ -513,6 +515,7 @@ public final class ViewRootImpl implements ViewParent,
private boolean mPendingDragResizing;
private boolean mDragResizing;
private boolean mInvalidateRootRequested;
+ private int mResizeMode = RESIZE_MODE_INVALID;
private int mCanvasOffsetX;
private int mCanvasOffsetY;
private boolean mActivityRelaunched;
@@ -592,19 +595,21 @@ public final class ViewRootImpl implements ViewParent,
String mLastPerformDrawSkippedReason;
/** The reason the last call to performTraversals() returned without drawing */
String mLastPerformTraversalsSkipDrawReason;
- /** The state of the local sync, if one is in progress. Can be one of the states below. */
- int mLocalSyncState;
+ /** The state of the WMS requested sync, if one is in progress. Can be one of the states
+ * below. */
+ int mWmsRequestSyncGroupState;
- // The possible states of the local sync, see createSyncIfNeeded()
- private final int LOCAL_SYNC_NONE = 0;
- private final int LOCAL_SYNC_PENDING = 1;
- private final int LOCAL_SYNC_RETURNED = 2;
- private final int LOCAL_SYNC_MERGED = 3;
+ // The possible states of the WMS requested sync, see createSyncIfNeeded()
+ private static final int WMS_SYNC_NONE = 0;
+ private static final int WMS_SYNC_PENDING = 1;
+ private static final int WMS_SYNC_RETURNED = 2;
+ private static final int WMS_SYNC_MERGED = 3;
/**
- * Set whether the draw should send the buffer to system server. When set to true, VRI will
- * create a sync transaction with BBQ and send the resulting buffer to system server. If false,
- * VRI will not try to sync a buffer in BBQ, but still report when a draw occurred.
+ * Set whether the requested SurfaceSyncGroup should sync the buffer. When set to true, VRI will
+ * create a sync transaction with BBQ and send the resulting buffer back to the
+ * SurfaceSyncGroup. If false, VRI will not try to sync a buffer in BBQ, but still report when a
+ * draw occurred.
*/
private boolean mSyncBuffer = false;
@@ -846,8 +851,19 @@ public final class ViewRootImpl implements ViewParent,
return mHandwritingInitiator;
}
- private SurfaceSyncGroup mSyncGroup;
- private SurfaceSyncGroup.TransactionReadyCallback mTransactionReadyCallback;
+ /**
+ * A SurfaceSyncGroup that is created when WMS requested to sync the buffer
+ */
+ private SurfaceSyncGroup mWmsRequestSyncGroup;
+
+ /**
+ * The SurfaceSyncGroup that represents the active VRI SurfaceSyncGroup. This is non null if
+ * anyone requested the SurfaceSyncGroup for this VRI to ensure that anyone trying to sync with
+ * this VRI are collected together. The SurfaceSyncGroup is cleared when the VRI draws since
+ * that is the stop point where all changes are have been applied. A new SurfaceSyncGroup is
+ * created after that point when something wants to sync VRI again.
+ */
+ private SurfaceSyncGroup mActiveSurfaceSyncGroup;
private static final Object sSyncProgressLock = new Object();
// The count needs to be static since it's used to enable or disable RT animations which is
@@ -1790,7 +1806,7 @@ public final class ViewRootImpl implements ViewParent,
CompatibilityInfo.applyOverrideScaleIfNeeded(mergedConfiguration);
final boolean forceNextWindowRelayout = args.argi1 != 0;
final int displayId = args.argi3;
- final boolean dragResizing = args.argi5 != 0;
+ final int resizeMode = args.argi5;
final Rect frame = frames.frame;
final Rect displayFrame = frames.displayFrame;
@@ -1806,14 +1822,16 @@ public final class ViewRootImpl implements ViewParent,
final boolean attachedFrameChanged = LOCAL_LAYOUT
&& !Objects.equals(mTmpFrames.attachedFrame, attachedFrame);
final boolean displayChanged = mDisplay.getDisplayId() != displayId;
+ final boolean resizeModeChanged = mResizeMode != resizeMode;
final boolean compatScaleChanged = mTmpFrames.compatScale != compatScale;
if (msg == MSG_RESIZED && !frameChanged && !configChanged && !attachedFrameChanged
- && !displayChanged && !forceNextWindowRelayout
+ && !displayChanged && !resizeModeChanged && !forceNextWindowRelayout
&& !compatScaleChanged) {
return;
}
- mPendingDragResizing = dragResizing;
+ mPendingDragResizing = resizeMode != RESIZE_MODE_INVALID;
+ mResizeMode = resizeMode;
mTmpFrames.compatScale = compatScale;
mInvCompatScale = 1f / compatScale;
@@ -3010,7 +3028,7 @@ public final class ViewRootImpl implements ViewParent,
frame.width() < desiredWindowWidth && frame.width() != mWidth)
|| (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT &&
frame.height() < desiredWindowHeight && frame.height() != mHeight));
- windowShouldResize |= mDragResizing && mPendingDragResizing;
+ windowShouldResize |= mDragResizing && mResizeMode == RESIZE_MODE_FREEFORM;
// If the activity was just relaunched, it might have unfrozen the task bounds (while
// relaunching), so we need to force a call into window manager to pick up the latest
@@ -3257,7 +3275,7 @@ public final class ViewRootImpl implements ViewParent,
&& mWinFrame.height() == mPendingBackDropFrame.height();
// TODO: Need cutout?
startDragResizing(mPendingBackDropFrame, !backdropSizeMatchesFrame,
- mAttachInfo.mContentInsets, mAttachInfo.mStableInsets);
+ mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mResizeMode);
} else {
// We shouldn't come here, but if we come we should end the resize.
endDragResizing();
@@ -3638,6 +3656,12 @@ public final class ViewRootImpl implements ViewParent,
boolean cancelAndRedraw = cancelDueToPreDrawListener
|| (cancelDraw && mDrewOnceForSync);
if (!cancelAndRedraw) {
+ // A sync was already requested before the WMS requested sync. This means we need to
+ // sync the buffer, regardless if WMS wants to sync the buffer.
+ if (mActiveSurfaceSyncGroup != null) {
+ mSyncBuffer = true;
+ }
+
createSyncIfNeeded();
mDrewOnceForSync = true;
}
@@ -3651,8 +3675,8 @@ public final class ViewRootImpl implements ViewParent,
mPendingTransitions.clear();
}
- if (mTransactionReadyCallback != null) {
- mTransactionReadyCallback.onTransactionReady(null);
+ if (mActiveSurfaceSyncGroup != null) {
+ mActiveSurfaceSyncGroup.onTransactionReady(null);
}
} else if (cancelAndRedraw) {
mLastPerformTraversalsSkipDrawReason = cancelDueToPreDrawListener
@@ -3667,8 +3691,8 @@ public final class ViewRootImpl implements ViewParent,
}
mPendingTransitions.clear();
}
- if (!performDraw() && mTransactionReadyCallback != null) {
- mTransactionReadyCallback.onTransactionReady(null);
+ if (!performDraw() && mActiveSurfaceSyncGroup != null) {
+ mActiveSurfaceSyncGroup.onTransactionReady(null);
}
}
@@ -3682,39 +3706,40 @@ public final class ViewRootImpl implements ViewParent,
if (!cancelAndRedraw) {
mReportNextDraw = false;
mLastReportNextDrawReason = null;
- mTransactionReadyCallback = null;
+ mActiveSurfaceSyncGroup = null;
mSyncBuffer = false;
- if (isInLocalSync()) {
- mSyncGroup.markSyncReady();
- mSyncGroup = null;
- mLocalSyncState = LOCAL_SYNC_NONE;
+ if (isInWMSRequestedSync()) {
+ mWmsRequestSyncGroup.onTransactionReady(null);
+ mWmsRequestSyncGroup = null;
+ mWmsRequestSyncGroupState = WMS_SYNC_NONE;
}
}
}
private void createSyncIfNeeded() {
- // Started a sync already or there's nothing needing to sync
- if (isInLocalSync() || !mReportNextDraw) {
+ // WMS requested sync already started or there's nothing needing to sync
+ if (isInWMSRequestedSync() || !mReportNextDraw) {
return;
}
final int seqId = mSyncSeqId;
- mLocalSyncState = LOCAL_SYNC_PENDING;
- mSyncGroup = new SurfaceSyncGroup(transaction -> {
- mLocalSyncState = LOCAL_SYNC_RETURNED;
+ mWmsRequestSyncGroupState = WMS_SYNC_PENDING;
+ mWmsRequestSyncGroup = new SurfaceSyncGroup(t -> {
+ mWmsRequestSyncGroupState = WMS_SYNC_RETURNED;
// Callback will be invoked on executor thread so post to main thread.
mHandler.postAtFrontOfQueue(() -> {
- if (transaction != null) {
- mSurfaceChangedTransaction.merge(transaction);
+ if (t != null) {
+ mSurfaceChangedTransaction.merge(t);
}
- mLocalSyncState = LOCAL_SYNC_MERGED;
+ mWmsRequestSyncGroupState = WMS_SYNC_MERGED;
reportDrawFinished(seqId);
});
});
if (DEBUG_BLAST) {
- Log.d(mTag, "Setup new sync id=" + mSyncGroup);
+ Log.d(mTag, "Setup new sync id=" + mWmsRequestSyncGroup);
}
- mSyncGroup.addToSync(mSyncTarget);
+
+ mWmsRequestSyncGroup.addToSync(this);
notifySurfaceSyncStarted();
}
@@ -4365,19 +4390,11 @@ public final class ViewRootImpl implements ViewParent,
return mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled();
}
- void addToSync(SurfaceSyncGroup.SyncTarget syncable) {
- if (!isInLocalSync()) {
- return;
- }
- mSyncGroup.addToSync(syncable);
- }
-
/**
- * This VRI is currently in the middle of a sync request, but specifically one initiated from
- * within VRI.
+ * This VRI is currently in the middle of a sync request that was initiated by WMS.
*/
- public boolean isInLocalSync() {
- return mSyncGroup != null;
+ public boolean isInWMSRequestedSync() {
+ return mWmsRequestSyncGroup != null;
}
private void addFrameCommitCallbackIfNeeded() {
@@ -4444,7 +4461,7 @@ public final class ViewRootImpl implements ViewParent,
return false;
}
- final boolean fullRedrawNeeded = mFullRedrawNeeded || mTransactionReadyCallback != null;
+ final boolean fullRedrawNeeded = mFullRedrawNeeded || mActiveSurfaceSyncGroup != null;
mFullRedrawNeeded = false;
mIsDrawing = true;
@@ -4452,9 +4469,9 @@ public final class ViewRootImpl implements ViewParent,
addFrameCommitCallbackIfNeeded();
- boolean usingAsyncReport = isHardwareEnabled() && mTransactionReadyCallback != null;
+ boolean usingAsyncReport = isHardwareEnabled() && mActiveSurfaceSyncGroup != null;
if (usingAsyncReport) {
- registerCallbacksForSync(mSyncBuffer, mTransactionReadyCallback);
+ registerCallbacksForSync(mSyncBuffer, mActiveSurfaceSyncGroup);
} else if (mHasPendingTransactions) {
// These callbacks are only needed if there's no sync involved and there were calls to
// applyTransactionOnDraw. These callbacks check if the draw failed for any reason and
@@ -4505,11 +4522,10 @@ public final class ViewRootImpl implements ViewParent,
}
if (mSurfaceHolder != null && mSurface.isValid()) {
- final SurfaceSyncGroup.TransactionReadyCallback transactionReadyCallback =
- mTransactionReadyCallback;
+ final SurfaceSyncGroup surfaceSyncGroup = mActiveSurfaceSyncGroup;
SurfaceCallbackHelper sch = new SurfaceCallbackHelper(() ->
- mHandler.post(() -> transactionReadyCallback.onTransactionReady(null)));
- mTransactionReadyCallback = null;
+ mHandler.post(() -> surfaceSyncGroup.onTransactionReady(null)));
+ mActiveSurfaceSyncGroup = null;
SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
@@ -4520,8 +4536,8 @@ public final class ViewRootImpl implements ViewParent,
}
}
}
- if (mTransactionReadyCallback != null && !usingAsyncReport) {
- mTransactionReadyCallback.onTransactionReady(null);
+ if (mActiveSurfaceSyncGroup != null && !usingAsyncReport) {
+ mActiveSurfaceSyncGroup.onTransactionReady(null);
}
if (mPerformContentCapture) {
performContentCaptureInitialReport();
@@ -8611,8 +8627,8 @@ public final class ViewRootImpl implements ViewParent,
writer.println(innerPrefix + "mLastPerformDrawFailedReason="
+ mLastPerformDrawSkippedReason);
}
- if (mLocalSyncState != LOCAL_SYNC_NONE) {
- writer.println(innerPrefix + "mLocalSyncState=" + mLocalSyncState);
+ if (mWmsRequestSyncGroupState != WMS_SYNC_NONE) {
+ writer.println(innerPrefix + "mWmsRequestSyncGroupState=" + mWmsRequestSyncGroupState);
}
writer.println(innerPrefix + "mLastReportedMergedConfiguration="
+ mLastReportedMergedConfiguration);
@@ -8825,7 +8841,7 @@ public final class ViewRootImpl implements ViewParent,
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private void dispatchResized(ClientWindowFrames frames, boolean reportDraw,
MergedConfiguration mergedConfiguration, InsetsState insetsState, boolean forceLayout,
- boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, boolean dragResizing) {
+ boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, int resizeMode) {
Message msg = mHandler.obtainMessage(reportDraw ? MSG_RESIZED_REPORT : MSG_RESIZED);
SomeArgs args = SomeArgs.obtain();
final boolean sameProcessCall = (Binder.getCallingPid() == android.os.Process.myPid());
@@ -8847,7 +8863,7 @@ public final class ViewRootImpl implements ViewParent,
args.argi2 = alwaysConsumeSystemBars ? 1 : 0;
args.argi3 = displayId;
args.argi4 = syncSeqId;
- args.argi5 = dragResizing ? 1 : 0;
+ args.argi5 = resizeMode;
msg.obj = args;
mHandler.sendMessage(msg);
@@ -10239,11 +10255,11 @@ public final class ViewRootImpl implements ViewParent,
public void resized(ClientWindowFrames frames, boolean reportDraw,
MergedConfiguration mergedConfiguration, InsetsState insetsState,
boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId,
- boolean dragResizing) {
+ int resizeMode) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
viewAncestor.dispatchResized(frames, reportDraw, mergedConfiguration, insetsState,
- forceLayout, alwaysConsumeSystemBars, displayId, syncSeqId, dragResizing);
+ forceLayout, alwaysConsumeSystemBars, displayId, syncSeqId, resizeMode);
}
}
@@ -10448,13 +10464,13 @@ public final class ViewRootImpl implements ViewParent,
* Start a drag resizing which will inform all listeners that a window resize is taking place.
*/
private void startDragResizing(Rect initialBounds, boolean fullscreen, Rect systemInsets,
- Rect stableInsets) {
+ Rect stableInsets, int resizeMode) {
if (!mDragResizing) {
mDragResizing = true;
if (mUseMTRenderer) {
for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
mWindowCallbacks.get(i).onWindowDragResizeStart(
- initialBounds, fullscreen, systemInsets, stableInsets);
+ initialBounds, fullscreen, systemInsets, stableInsets, resizeMode);
}
}
mFullRedrawNeeded = true;
@@ -11200,7 +11216,7 @@ public final class ViewRootImpl implements ViewParent,
}
private void registerCallbacksForSync(boolean syncBuffer,
- final SurfaceSyncGroup.TransactionReadyCallback transactionReadyCallback) {
+ final SurfaceSyncGroup surfaceSyncGroup) {
if (!isHardwareEnabled()) {
return;
}
@@ -11227,7 +11243,7 @@ public final class ViewRootImpl implements ViewParent,
// pendingDrawFinished.
if ((syncResult
& (SYNC_LOST_SURFACE_REWARD_IF_FOUND | SYNC_CONTEXT_IS_STOPPED)) != 0) {
- transactionReadyCallback.onTransactionReady(
+ surfaceSyncGroup.onTransactionReady(
mBlastBufferQueue.gatherPendingTransactions(frame));
return null;
}
@@ -11238,7 +11254,7 @@ public final class ViewRootImpl implements ViewParent,
if (syncBuffer) {
mBlastBufferQueue.syncNextTransaction(
- transactionReadyCallback::onTransactionReady);
+ surfaceSyncGroup::onTransactionReady);
}
return didProduceBuffer -> {
@@ -11258,7 +11274,7 @@ public final class ViewRootImpl implements ViewParent,
// since the frame didn't draw on this vsync. It's possible the frame will
// draw later, but it's better to not be sync than to block on a frame that
// may never come.
- transactionReadyCallback.onTransactionReady(
+ surfaceSyncGroup.onTransactionReady(
mBlastBufferQueue.gatherPendingTransactions(frame));
return;
}
@@ -11267,35 +11283,23 @@ public final class ViewRootImpl implements ViewParent,
// syncNextTransaction callback. Instead, just report back to the Syncer so it
// knows that this sync request is complete.
if (!syncBuffer) {
- transactionReadyCallback.onTransactionReady(null);
+ surfaceSyncGroup.onTransactionReady(null);
}
};
}
});
}
- public final SurfaceSyncGroup.SyncTarget mSyncTarget = new SurfaceSyncGroup.SyncTarget() {
- @Override
- public void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
- SurfaceSyncGroup.TransactionReadyCallback transactionReadyCallback) {
- updateSyncInProgressCount(parentSyncGroup);
- if (!isInLocalSync()) {
- // Always sync the buffer if the sync request did not come from VRI.
- mSyncBuffer = true;
- }
-
- if (mTransactionReadyCallback != null) {
- Log.d(mTag, "Already set sync for the next draw.");
- mTransactionReadyCallback.onTransactionReady(null);
- }
- if (DEBUG_BLAST) {
- Log.d(mTag, "Setting syncFrameCallback");
- }
- mTransactionReadyCallback = transactionReadyCallback;
+ @Override
+ public SurfaceSyncGroup getOrCreateSurfaceSyncGroup() {
+ if (mActiveSurfaceSyncGroup == null) {
+ mActiveSurfaceSyncGroup = new SurfaceSyncGroup();
+ updateSyncInProgressCount(mActiveSurfaceSyncGroup);
if (!mIsInTraversal && !mTraversalScheduled) {
scheduleTraversals();
}
}
+ return mActiveSurfaceSyncGroup;
};
private final Executor mSimpleExecutor = Runnable::run;
@@ -11320,15 +11324,10 @@ public final class ViewRootImpl implements ViewParent,
});
}
- @Override
- public SurfaceSyncGroup.SyncTarget getSyncTarget() {
- return mSyncTarget;
- }
-
- void mergeSync(SurfaceSyncGroup otherSyncGroup) {
- if (!isInLocalSync()) {
+ void addToSync(SurfaceSyncGroup syncable) {
+ if (mActiveSurfaceSyncGroup == null) {
return;
}
- mSyncGroup.merge(otherSyncGroup);
+ mActiveSurfaceSyncGroup.addToSync(syncable, false /* parentSyncGroupMerge */);
}
}
diff --git a/core/java/android/view/WindowCallbacks.java b/core/java/android/view/WindowCallbacks.java
index 94b2d93455d3..a7f0ef0a0324 100644
--- a/core/java/android/view/WindowCallbacks.java
+++ b/core/java/android/view/WindowCallbacks.java
@@ -28,6 +28,22 @@ import android.graphics.Rect;
*/
public interface WindowCallbacks {
+ int RESIZE_MODE_INVALID = -1;
+
+ /**
+ * The window is being resized by dragging one of the window corners,
+ * in this case the surface would be fullscreen-sized. The client should
+ * render to the actual frame location (instead of (0,curScrollY)).
+ */
+ int RESIZE_MODE_FREEFORM = 0;
+
+ /**
+ * The window is being resized by dragging on the docked divider. The client should render
+ * at (0, 0) and extend its background to the background frame passed into
+ * {@link IWindow#resized}.
+ */
+ int RESIZE_MODE_DOCKED_DIVIDER = 1;
+
/**
* Called by the system when the window got changed by the user, before the layouter got called.
* It also gets called when the insets changed, or when the window switched between a fullscreen
@@ -53,7 +69,7 @@ public interface WindowCallbacks {
* @param stableInsets The stable insets for the window.
*/
void onWindowDragResizeStart(Rect initialBounds, boolean fullscreen, Rect systemInsets,
- Rect stableInsets);
+ Rect stableInsets, int resizeMode);
/**
* Called when a drag resize ends.
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 2d6c1d913e90..69340aa39daf 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -16,6 +16,8 @@
package android.view;
+import static android.view.WindowCallbacks.RESIZE_MODE_INVALID;
+
import android.annotation.Nullable;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
@@ -548,7 +550,7 @@ public class WindowlessWindowManager implements IWindowSession {
mTmpConfig.setConfiguration(mConfiguration, mConfiguration);
s.mClient.resized(mTmpFrames, false /* reportDraw */, mTmpConfig, state,
false /* forceLayout */, false /* alwaysConsumeSystemBars */, s.mDisplayId,
- Integer.MAX_VALUE, false /* dragResizing */);
+ Integer.MAX_VALUE, RESIZE_MODE_INVALID);
} catch (RemoteException e) {
// Too bad
}
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 423c560d5c57..9abbba923a66 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -25,6 +25,7 @@ import android.accessibilityservice.AccessibilityServiceInfo.FeedbackType;
import android.accessibilityservice.AccessibilityShortcutInfo;
import android.annotation.CallbackExecutor;
import android.annotation.ColorInt;
+import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -75,6 +76,7 @@ import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
import java.util.concurrent.Executor;
/**
@@ -138,6 +140,21 @@ public final class AccessibilityManager {
public static final int AUTOCLICK_DELAY_DEFAULT = 600;
/**
+ * The contrast is defined as a float in [-1, 1], with a default value of 0.
+ * @hide
+ */
+ public static final float CONTRAST_MIN_VALUE = -1f;
+
+ /** @hide */
+ public static final float CONTRAST_MAX_VALUE = 1f;
+
+ /** @hide */
+ public static final float CONTRAST_DEFAULT_VALUE = 0f;
+
+ /** @hide */
+ public static final float CONTRAST_NOT_SET = Float.MIN_VALUE;
+
+ /**
* Activity action: Launch UI to manage which accessibility service or feature is assigned
* to the navigation bar Accessibility button.
* <p>
@@ -246,6 +263,8 @@ public final class AccessibilityManager {
@UnsupportedAppUsage(trackingBug = 123768939L)
boolean mIsHighTextContrastEnabled;
+ private float mUiContrast;
+
boolean mIsAudioDescriptionByDefaultRequested;
// accessibility tracing state
@@ -270,6 +289,9 @@ public final class AccessibilityManager {
private final ArrayMap<HighTextContrastChangeListener, Handler>
mHighTextContrastStateChangeListeners = new ArrayMap<>();
+ private final ArrayMap<UiContrastChangeListener, Executor>
+ mUiContrastChangeListeners = new ArrayMap<>();
+
private final ArrayMap<AccessibilityServicesStateChangeListener, Executor>
mServicesStateChangeListeners = new ArrayMap<>();
@@ -336,7 +358,7 @@ public final class AccessibilityManager {
*
* @param manager The manager that is calling back
*/
- void onAccessibilityServicesStateChanged(@NonNull AccessibilityManager manager);
+ void onAccessibilityServicesStateChanged(@NonNull AccessibilityManager manager);
}
/**
@@ -358,6 +380,21 @@ public final class AccessibilityManager {
}
/**
+ * Listener for the UI contrast. To listen for changes to
+ * the UI contrast on the device, implement this interface and
+ * register it with the system by calling {@link #addUiContrastChangeListener}.
+ */
+ public interface UiContrastChangeListener {
+
+ /**
+ * Called when the color contrast enabled state changes.
+ *
+ * @param uiContrast The color contrast as in {@link #getUiContrast}
+ */
+ void onUiContrastChanged(@FloatRange(from = -1.0f, to = 1.0f) float uiContrast);
+ }
+
+ /**
* Listener for the audio description by default state. To listen for
* changes to the audio description by default state on the device,
* implement this interface and register it with the system by calling
@@ -471,6 +508,16 @@ public final class AccessibilityManager {
updateFocusAppearanceLocked(strokeWidth, color);
}
}
+
+ @Override
+ public void setUiContrast(float contrast) {
+ synchronized (mLock) {
+ // if value changed in the settings, update the cached value and notify listeners
+ if (Math.abs(mUiContrast - contrast) < 1e-10) return;
+ mUiContrast = contrast;
+ }
+ mHandler.obtainMessage(MyCallback.MSG_NOTIFY_CONTRAST_CHANGED).sendToTarget();
+ }
};
/**
@@ -641,7 +688,7 @@ public final class AccessibilityManager {
/**
* Returns if the high text contrast in the system is enabled.
* <p>
- * <strong>Note:</strong> You need to query this only if you application is
+ * <strong>Note:</strong> You need to query this only if your application is
* doing its own rendering and does not rely on the platform rendering pipeline.
* </p>
*
@@ -661,6 +708,24 @@ public final class AccessibilityManager {
}
/**
+ * Returns the color contrast for the user.
+ * <p>
+ * <strong>Note:</strong> You need to query this only if your application is
+ * doing its own rendering and does not rely on the platform rendering pipeline.
+ * </p>
+ * @return The color contrast, float in [-1, 1] where
+ * 0 corresponds to the default contrast
+ * -1 corresponds to the minimum contrast that the user can set
+ * 1 corresponds to the maximum contrast that the user can set
+ */
+ @FloatRange(from = -1.0f, to = 1.0f)
+ public float getUiContrast() {
+ synchronized (mLock) {
+ return mUiContrast;
+ }
+ }
+
+ /**
* Sends an {@link AccessibilityEvent}.
*
* @param event The event to send.
@@ -1240,6 +1305,35 @@ public final class AccessibilityManager {
}
/**
+ * Registers a {@link UiContrastChangeListener} for the current user.
+ *
+ * @param executor The executor on which the listener should be called back.
+ * @param listener The listener.
+ */
+ public void addUiContrastChangeListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull UiContrastChangeListener listener) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(listener);
+ synchronized (mLock) {
+ mUiContrastChangeListeners.put(listener, executor);
+ }
+ }
+
+ /**
+ * Unregisters a {@link UiContrastChangeListener} for the current user.
+ * If the listener was not registered, does nothing and returns.
+ *
+ * @param listener The listener to unregister.
+ */
+ public void removeUiContrastChangeListener(@NonNull UiContrastChangeListener listener) {
+ Objects.requireNonNull(listener);
+ synchronized (mLock) {
+ mUiContrastChangeListeners.remove(listener);
+ }
+ }
+
+ /**
* Registers a {@link AudioDescriptionRequestedChangeListener}
* for changes in the audio description by default state of the system.
* The value could be read via {@link #isAudioDescriptionRequested}.
@@ -2004,6 +2098,7 @@ public final class AccessibilityManager {
mRelevantEventTypes = IntPair.second(userStateAndRelevantEvents);
updateUiTimeout(service.getRecommendedTimeoutMillis());
updateFocusAppearanceLocked(service.getFocusStrokeWidth(), service.getFocusColor());
+ mUiContrast = service.getUiContrast();
mService = service;
} catch (RemoteException re) {
Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
@@ -2082,6 +2177,22 @@ public final class AccessibilityManager {
}
/**
+ * Notifies the registered {@link UiContrastChangeListener}s if the value changed.
+ */
+ private void notifyUiContrastChanged() {
+ final ArrayMap<UiContrastChangeListener, Executor> listeners;
+ synchronized (mLock) {
+ listeners = new ArrayMap<>(mUiContrastChangeListeners);
+ }
+
+ listeners.entrySet().forEach(entry -> {
+ UiContrastChangeListener listener = entry.getKey();
+ Executor executor = entry.getValue();
+ executor.execute(() -> listener.onUiContrastChanged(mUiContrast));
+ });
+ }
+
+ /**
* Notifies the registered {@link AudioDescriptionStateChangeListener}s.
*/
private void notifyAudioDescriptionbyDefaultStateChanged() {
@@ -2171,6 +2282,7 @@ public final class AccessibilityManager {
private final class MyCallback implements Handler.Callback {
public static final int MSG_SET_STATE = 1;
+ public static final int MSG_NOTIFY_CONTRAST_CHANGED = 2;
@Override
public boolean handleMessage(Message message) {
@@ -2182,6 +2294,9 @@ public final class AccessibilityManager {
setStateLocked(state);
}
} break;
+ case MSG_NOTIFY_CONTRAST_CHANGED: {
+ notifyUiContrastChanged();
+ }
}
return true;
}
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 364c7c8e1fb9..c2d899a50d4e 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -118,4 +118,6 @@ interface IAccessibilityManager {
// Used by UiAutomation for tests on the InputFilter
void injectInputEventToInputFilter(in InputEvent event);
+
+ float getUiContrast();
}
diff --git a/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl b/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl
index 041399ccb8ec..931f862e581b 100644
--- a/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl
@@ -31,4 +31,6 @@ oneway interface IAccessibilityManagerClient {
void setRelevantEventTypes(int eventTypes);
void setFocusAppearance(int strokeWidth, int color);
+
+ void setUiContrast(float contrast);
}
diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java
index 395073941930..250652a53677 100644
--- a/core/java/android/window/SurfaceSyncGroup.java
+++ b/core/java/android/window/SurfaceSyncGroup.java
@@ -40,62 +40,63 @@ import java.util.function.Supplier;
* mechanism so each sync implementation doesn't need to handle it themselves. The SurfaceSyncGroup
* class is used the following way.
*
- * 1. {@link #SurfaceSyncGroup()} constructor is called
- * 2. {@link #addToSync(SyncTarget)} is called for every SyncTarget object that wants to be
- * included in the sync. If the addSync is called for an {@link AttachedSurfaceControl} or
- * {@link SurfaceView} it needs to be called on the UI thread. When addToSync is called, it's
+ * 1. {@link #addToSync(SurfaceSyncGroup, boolean)} is called for every SurfaceSyncGroup object that
+ * wants to be included in the sync. If the addSync is called for an {@link AttachedSurfaceControl}
+ * or {@link SurfaceView} it needs to be called on the UI thread. When addToSync is called, it's
* guaranteed that any UI updates that were requested before addToSync but after the last frame
* drew, will be included in the sync.
- * 3. {@link #markSyncReady()} should be called when all the {@link SyncTarget}s have been added
- * to the SurfaceSyncGroup. At this point, the SurfaceSyncGroup is closed and no more SyncTargets
- * can be added to it.
- * 4. The SurfaceSyncGroup will gather the data for each SyncTarget using the steps described below.
- * When all the SyncTargets have finished, the syncRequestComplete will be invoked and the
- * transaction will either be applied or sent to the caller. In most cases, only the
- * SurfaceSyncGroup should be handling the Transaction object directly. However, there are some
+ * 2. {@link #markSyncReady()} should be called when all the {@link SurfaceSyncGroup}s have been
+ * added to the SurfaceSyncGroup. At this point, the SurfaceSyncGroup is closed and no more
+ * SurfaceSyncGroups can be added to it.
+ * 3. The SurfaceSyncGroup will gather the data for each SurfaceSyncGroup using the steps described
+ * below. When all the SurfaceSyncGroups have finished, the syncRequestComplete will be invoked and
+ * the transaction will either be applied or sent to the caller. In most cases, only the
+ * SurfaceSyncGroup should be handling the Transaction object directly. However, there are some
* cases where the framework needs to send the Transaction elsewhere, like in ViewRootImpl, so that
* option is provided.
*
- * The following is what happens within the {@link SurfaceSyncGroup}
- * 1. Each SyncTarget will get a {@link SyncTarget#onAddedToSyncGroup} callback that contains a
- * {@link TransactionReadyCallback}.
- * 2. Each {@link SyncTarget} needs to invoke
- * {@link TransactionReadyCallback#onTransactionReady(Transaction)}. This makes sure the
- * SurfaceSyncGroup knows when the SyncTarget is complete, allowing the SurfaceSyncGroup to get the
- * Transaction that contains the buffer.
- * 3. When the final TransactionReadyCallback finishes for the SurfaceSyncGroup, in most cases the
- * transaction is applied and then the sync complete callbacks are invoked, letting the callers know
- * the sync is now complete.
+ * The following is what happens within the {@link android.window.SurfaceSyncGroup}
+ * 1. Each SurfaceSyncGroup will get a
+ * {@link SurfaceSyncGroup#onAddedToSyncGroup(SurfaceSyncGroup, TransactionReadyCallback)} callback
+ * that contains a {@link TransactionReadyCallback}.
+ * 2. Each {@link SurfaceSyncGroup} needs to invoke
+ * {@link SurfaceSyncGroup#onTransactionReady(Transaction)}.
+ * This makes sure the parent SurfaceSyncGroup knows when the SurfaceSyncGroup is complete, allowing
+ * the parent SurfaceSyncGroup to get the Transaction that contains the changes for the child
+ * SurfaceSyncGroup
+ * 3. When the final TransactionReadyCallback finishes for the child SurfaceSyncGroups, the
+ * transaction is either applied if it's the top most parent or the final merged transaction is sent
+ * up to its parent SurfaceSyncGroup.
*
* @hide
*/
-public final class SurfaceSyncGroup {
+public class SurfaceSyncGroup {
private static final String TAG = "SurfaceSyncGroup";
private static final boolean DEBUG = false;
private static Supplier<Transaction> sTransactionFactory = Transaction::new;
/**
- * Class that collects the {@link SyncTarget}s and notifies when all the surfaces have
+ * Class that collects the {@link SurfaceSyncGroup}s and notifies when all the surfaces have
* a frame ready.
*/
private final Object mLock = new Object();
@GuardedBy("mLock")
- private final Set<Integer> mPendingSyncs = new ArraySet<>();
+ private final Set<TransactionReadyCallback> mPendingSyncs = new ArraySet<>();
@GuardedBy("mLock")
private final Transaction mTransaction = sTransactionFactory.get();
@GuardedBy("mLock")
private boolean mSyncReady;
@GuardedBy("mLock")
- private Consumer<Transaction> mSyncRequestCompleteCallback;
+ private boolean mFinished;
@GuardedBy("mLock")
- private final Set<SurfaceSyncGroup> mMergedSyncGroups = new ArraySet<>();
+ private TransactionReadyCallback mTransactionReadyCallback;
@GuardedBy("mLock")
- private boolean mFinished;
+ private SurfaceSyncGroup mParentSyncGroup;
@GuardedBy("mLock")
private final ArraySet<Pair<Executor, Runnable>> mSyncCompleteCallbacks = new ArraySet<>();
@@ -122,16 +123,16 @@ public final class SurfaceSyncGroup {
/**
* Creates a sync.
*
- * @param syncRequestComplete The complete callback that contains the syncId and transaction
- * with all the sync data merged. The Transaction passed back can be
- * null.
+ * @param transactionReadyCallback The complete callback that contains the syncId and
+ * transaction with all the sync data merged. The Transaction
+ * passed back can be null.
*
* NOTE: Only should be used by ViewRootImpl
* @hide
*/
- public SurfaceSyncGroup(Consumer<Transaction> syncRequestComplete) {
- mSyncRequestCompleteCallback = transaction -> {
- syncRequestComplete.accept(transaction);
+ public SurfaceSyncGroup(Consumer<Transaction> transactionReadyCallback) {
+ mTransactionReadyCallback = transaction -> {
+ transactionReadyCallback.accept(transaction);
synchronized (mLock) {
for (Pair<Executor, Runnable> callback : mSyncCompleteCallbacks) {
callback.first.execute(callback.second);
@@ -157,6 +158,31 @@ public final class SurfaceSyncGroup {
}
/**
+ * Mark the sync set as ready to complete. No more data can be added to the specified
+ * syncId.
+ * Once the sync set is marked as ready, it will be able to complete once all Syncables in the
+ * set have completed their sync
+ */
+ public void markSyncReady() {
+ onTransactionReady(null);
+ }
+
+ /**
+ * Similar to {@link #markSyncReady()}, but a transaction is passed in to merge with the
+ * SurfaceSyncGroup.
+ * @param t The transaction that merges into the main Transaction for the SurfaceSyncGroup.
+ */
+ public void onTransactionReady(@Nullable Transaction t) {
+ synchronized (mLock) {
+ mSyncReady = true;
+ if (t != null) {
+ mTransaction.merge(t);
+ }
+ checkIfSyncIsComplete();
+ }
+ }
+
+ /**
* Add a SurfaceView to a sync set. This is different than
* {@link #addToSync(AttachedSurfaceControl)} because it requires the caller to notify the start
* and finish drawing in order to sync.
@@ -171,7 +197,13 @@ public final class SurfaceSyncGroup {
@UiThread
public boolean addToSync(SurfaceView surfaceView,
Consumer<SurfaceViewFrameCallback> frameCallbackConsumer) {
- return addToSync(new SurfaceViewSyncTarget(surfaceView, frameCallbackConsumer));
+ SurfaceSyncGroup surfaceSyncGroup = new SurfaceSyncGroup();
+ if (addToSync(surfaceSyncGroup, false /* parentSyncGroupMerge */)) {
+ frameCallbackConsumer.accept(
+ () -> surfaceView.syncNextFrame(surfaceSyncGroup::onTransactionReady));
+ return true;
+ }
+ return false;
}
/**
@@ -185,29 +217,38 @@ public final class SurfaceSyncGroup {
if (viewRoot == null) {
return false;
}
- SyncTarget syncTarget = viewRoot.getSyncTarget();
- if (syncTarget == null) {
+ SurfaceSyncGroup surfaceSyncGroup = viewRoot.getOrCreateSurfaceSyncGroup();
+ if (surfaceSyncGroup == null) {
return false;
}
- return addToSync(syncTarget);
+ return addToSync(surfaceSyncGroup, false /* parentSyncGroupMerge */);
}
/**
- * Add a {@link SyncTarget} to a sync set. The sync set will wait for all
+ * Add a {@link SurfaceSyncGroup} to a sync set. The sync set will wait for all
* SyncableSurfaces to complete before notifying.
*
- * @param syncTarget A SyncTarget that implements how to handle syncing transactions.
- * @return true if the SyncTarget was successfully added to the SyncGroup, false otherwise.
+ * @param surfaceSyncGroup A SyncableSurface that implements how to handle syncing
+ * buffers.
+ * @return true if the SyncGroup was successfully added to the current SyncGroup, false
+ * otherwise.
*/
- public boolean addToSync(SyncTarget syncTarget) {
+ public boolean addToSync(SurfaceSyncGroup surfaceSyncGroup, boolean parentSyncGroupMerge) {
TransactionReadyCallback transactionReadyCallback = new TransactionReadyCallback() {
@Override
public void onTransactionReady(Transaction t) {
synchronized (mLock) {
if (t != null) {
+ // When an older parent sync group is added due to a child syncGroup getting
+ // added to multiple groups, we need to maintain merge order so the older
+ // parentSyncGroup transactions are overwritten by anything in the newer
+ // parentSyncGroup.
+ if (parentSyncGroupMerge) {
+ t.merge(mTransaction);
+ }
mTransaction.merge(t);
}
- mPendingSyncs.remove(hashCode());
+ mPendingSyncs.remove(this);
checkIfSyncIsComplete();
}
}
@@ -216,35 +257,38 @@ public final class SurfaceSyncGroup {
synchronized (mLock) {
if (mSyncReady) {
Log.e(TAG, "Sync " + this + " was already marked as ready. No more "
- + "SyncTargets can be added.");
+ + "SurfaceSyncGroups can be added.");
return false;
}
- mPendingSyncs.add(transactionReadyCallback.hashCode());
+ mPendingSyncs.add(transactionReadyCallback);
}
- syncTarget.onAddedToSyncGroup(this, transactionReadyCallback);
+ surfaceSyncGroup.onAddedToSyncGroup(this, transactionReadyCallback);
return true;
}
/**
- * Mark the sync set as ready to complete. No more data can be added to the specified
- * syncId.
- * Once the sync set is marked as ready, it will be able to complete once all Syncables in the
- * set have completed their sync
+ * Add a Transaction to this sync set. This allows the caller to provide other info that
+ * should be synced with the transactions.
*/
- public void markSyncReady() {
+ public void addTransactionToSync(Transaction t) {
synchronized (mLock) {
- mSyncReady = true;
- checkIfSyncIsComplete();
+ mTransaction.merge(t);
}
}
@GuardedBy("mLock")
private void checkIfSyncIsComplete() {
- if (!mSyncReady || !mPendingSyncs.isEmpty() || !mMergedSyncGroups.isEmpty()) {
+ if (mFinished) {
if (DEBUG) {
- Log.d(TAG, "Syncable is not complete. mSyncReady=" + mSyncReady
- + " mPendingSyncs=" + mPendingSyncs.size() + " mergedSyncs="
- + mMergedSyncGroups.size());
+ Log.d(TAG, "SurfaceSyncGroup=" + this + " is already complete");
+ }
+ return;
+ }
+
+ if (!mSyncReady || !mPendingSyncs.isEmpty()) {
+ if (DEBUG) {
+ Log.d(TAG, "SurfaceSyncGroup=" + this + " is not complete. mSyncReady="
+ + mSyncReady + " mPendingSyncs=" + mPendingSyncs.size());
}
return;
}
@@ -252,114 +296,48 @@ public final class SurfaceSyncGroup {
if (DEBUG) {
Log.d(TAG, "Successfully finished sync id=" + this);
}
-
- mSyncRequestCompleteCallback.accept(mTransaction);
+ mTransactionReadyCallback.onTransactionReady(mTransaction);
mFinished = true;
}
- /**
- * Add a Transaction to this sync set. This allows the caller to provide other info that
- * should be synced with the transactions.
- */
- public void addTransactionToSync(Transaction t) {
- synchronized (mLock) {
- mTransaction.merge(t);
- }
- }
-
- private void updateCallback(Consumer<Transaction> transactionConsumer) {
+ private void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
+ TransactionReadyCallback transactionReadyCallback) {
+ boolean finished = false;
synchronized (mLock) {
if (mFinished) {
- Log.e(TAG, "Attempting to merge SyncGroup " + this + " when sync is"
- + " already complete");
- transactionConsumer.accept(null);
- }
-
- final Consumer<Transaction> oldCallback = mSyncRequestCompleteCallback;
- mSyncRequestCompleteCallback = transaction -> {
- oldCallback.accept(null);
- transactionConsumer.accept(transaction);
- };
- }
- }
-
- /**
- * Merge a SyncGroup into this SyncGroup. Since SyncGroups could still have pending SyncTargets,
- * we need to make sure those can still complete before the mergeTo SyncGroup is considered
- * complete.
- *
- * We keep track of all the merged SyncGroups until they are marked as done, and then they
- * are removed from the set. This SyncGroup is not considered done until all the merged
- * SyncGroups are done.
- *
- * When the merged SyncGroup is complete, it will invoke the original syncRequestComplete
- * callback but send an empty transaction to ensure the changes are applied early. This
- * is needed in case the original sync is relying on the callback to continue processing.
- *
- * @param otherSyncGroup The other SyncGroup to merge into this one.
- */
- public void merge(SurfaceSyncGroup otherSyncGroup) {
- synchronized (mLock) {
- mMergedSyncGroups.add(otherSyncGroup);
- }
- otherSyncGroup.updateCallback(transaction -> {
- synchronized (mLock) {
- mMergedSyncGroups.remove(otherSyncGroup);
- if (transaction != null) {
- mTransaction.merge(transaction);
+ finished = true;
+ } else {
+ // If this SurfaceSyncGroup was already added to a different SurfaceSyncGroup, we
+ // need to combine everything. We can add the old SurfaceSyncGroup parent to the new
+ // parent so the new parent doesn't complete until the old parent does.
+ // Additionally, the old parent will not get the final transaction object and
+ // instead will send it to the new parent, ensuring that any other SurfaceSyncGroups
+ // from the original parent are also combined with the new parent SurfaceSyncGroup.
+ if (mParentSyncGroup != null) {
+ Log.d(TAG, "Already part of sync group " + mParentSyncGroup + " " + this);
+ parentSyncGroup.addToSync(mParentSyncGroup, true /* parentSyncGroupMerge */);
}
- checkIfSyncIsComplete();
+ mParentSyncGroup = parentSyncGroup;
+ final TransactionReadyCallback lastCallback = mTransactionReadyCallback;
+ mTransactionReadyCallback = t -> {
+ lastCallback.onTransactionReady(null);
+ transactionReadyCallback.onTransactionReady(t);
+ };
}
- });
- }
-
- /**
- * Wrapper class to help synchronize SurfaceViews
- */
- private static class SurfaceViewSyncTarget implements SyncTarget {
- private final SurfaceView mSurfaceView;
- private final Consumer<SurfaceViewFrameCallback> mFrameCallbackConsumer;
-
- SurfaceViewSyncTarget(SurfaceView surfaceView,
- Consumer<SurfaceViewFrameCallback> frameCallbackConsumer) {
- mSurfaceView = surfaceView;
- mFrameCallbackConsumer = frameCallbackConsumer;
}
- @Override
- public void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
- TransactionReadyCallback transactionReadyCallback) {
- mFrameCallbackConsumer.accept(
- () -> mSurfaceView.syncNextFrame(transactionReadyCallback::onTransactionReady));
+ // Invoke the callback outside of the lock when the SurfaceSyncGroup being added was already
+ // complete.
+ if (finished) {
+ transactionReadyCallback.onTransactionReady(null);
}
}
-
- /**
- * A SyncTarget that can be added to a sync set.
- */
- public interface SyncTarget {
- /**
- * Called when the SyncTarget has been added to a SyncGroup as is ready to begin handing a
- * sync request. When invoked, the implementor is required to call
- * {@link TransactionReadyCallback#onTransactionReady(Transaction)} in order for this
- * SurfaceSyncGroup to fully complete.
- *
- * Always invoked on the thread that initiated the call to {@link #addToSync(SyncTarget)}
- *
- * @param parentSyncGroup The sync group this target has been added to.
- * @param transactionReadyCallback A TransactionReadyCallback that the caller must invoke
- * onTransactionReady
- */
- void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
- TransactionReadyCallback transactionReadyCallback);
- }
-
/**
* Interface so the SurfaceSyncer can know when it's safe to start and when everything has been
* completed. The caller should invoke the calls when the rendering has started and finished a
* frame.
*/
- public interface TransactionReadyCallback {
+ private interface TransactionReadyCallback {
/**
* Invoked when the transaction is ready to sync.
*
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 9f23f2474257..b9ca557da4bd 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -252,11 +252,13 @@ interface IVoiceInteractionManagerService {
* @param sharedMemory The unrestricted data blob to provide to the
* {@link HotwordDetectionService}. Use this to provide the hotword models data or other
* such data to the trusted process.
+ * @param token Use this to identify which detector calls this method.
*/
@EnforcePermission("MANAGE_HOTWORD_DETECTION")
void updateState(
in PersistableBundle options,
- in SharedMemory sharedMemory);
+ in SharedMemory sharedMemory,
+ in IBinder token);
/**
* Set configuration and pass read-only data to hotword detection service when creating
@@ -272,6 +274,7 @@ interface IVoiceInteractionManagerService {
* @param sharedMemory The unrestricted data blob to provide to the
* {@link HotwordDetectionService}. Use this to provide the hotword models data or other
* such data to the trusted process.
+ * @param token Use this to identify which detector calls this method.
* @param callback Use this to report {@link HotwordDetectionService} status.
* @param detectorType Indicate which detector is used.
*/
@@ -280,10 +283,18 @@ interface IVoiceInteractionManagerService {
in Identity originatorIdentity,
in PersistableBundle options,
in SharedMemory sharedMemory,
+ in IBinder token,
in IHotwordRecognitionStatusCallback callback,
int detectorType);
/**
+ * Destroy the detector callback.
+ *
+ * @param token Indicate which callback will be destroyed.
+ */
+ void destroyDetector(in IBinder token);
+
+ /**
* Requests to shutdown hotword detection service.
*/
void shutdownHotwordDetectionService();
@@ -298,6 +309,7 @@ interface IVoiceInteractionManagerService {
in ParcelFileDescriptor audioStream,
in AudioFormat audioFormat,
in PersistableBundle options,
+ in IBinder token,
in IMicrophoneHotwordDetectionVoiceInteractionCallback callback);
/**
diff --git a/core/java/com/android/internal/app/IVoiceInteractionSessionListener.aidl b/core/java/com/android/internal/app/IVoiceInteractionSessionListener.aidl
index 6e409885fa13..46f78e2ee8a2 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionSessionListener.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionSessionListener.aidl
@@ -31,6 +31,8 @@
/**
* Called when a voice session window is shown/hidden.
+ * Caution that there could be duplicated visibility change callbacks, it's up to the listener
+ * to dedup those events.
*/
void onVoiceSessionWindowVisibilityChanged(boolean visible);
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index b5b27f5289c9..145aeafb46a1 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -274,6 +274,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
private boolean mApplyFloatingVerticalInsets = false;
private boolean mApplyFloatingHorizontalInsets = false;
+ private int mResizeMode = RESIZE_MODE_INVALID;
private final int mResizeShadowSize;
private final Paint mVerticalResizeShadowPaint = new Paint();
private final Paint mHorizontalResizeShadowPaint = new Paint();
@@ -807,7 +808,9 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
updateElevation();
mAllowUpdateElevation = true;
- if (changed && mDrawLegacyNavigationBarBackground) {
+ if (changed
+ && (mResizeMode == RESIZE_MODE_DOCKED_DIVIDER
+ || mDrawLegacyNavigationBarBackground)) {
getViewRootImpl().requestInvalidateRootRenderNode();
}
}
@@ -2389,7 +2392,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
@Override
public void onWindowDragResizeStart(Rect initialBounds, boolean fullscreen, Rect systemInsets,
- Rect stableInsets) {
+ Rect stableInsets, int resizeMode) {
if (mWindow.isDestroyed()) {
// If the owner's window is gone, we should not be able to come here anymore.
releaseThreadedRenderer();
@@ -2415,6 +2418,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
updateColorViews(null /* insets */, false);
}
+ mResizeMode = resizeMode;
getViewRootImpl().requestInvalidateRootRenderNode();
}
@@ -2422,6 +2426,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
public void onWindowDragResizeEnd() {
releaseThreadedRenderer();
updateColorViews(null /* insets */, false);
+ mResizeMode = RESIZE_MODE_INVALID;
getViewRootImpl().requestInvalidateRootRenderNode();
}
@@ -2466,7 +2471,9 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
}
private void drawResizingShadowIfNeeded(RecordingCanvas canvas) {
- if (mWindow.mIsFloating || mWindow.isTranslucent() || mWindow.isShowingWallpaper()) {
+ if (mResizeMode != RESIZE_MODE_DOCKED_DIVIDER || mWindow.mIsFloating
+ || mWindow.isTranslucent()
+ || mWindow.isShowingWallpaper()) {
return;
}
canvas.save();
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index 4a5ed7e8970d..2ac43099c741 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -53,7 +53,7 @@ public class BaseIWindow extends IWindow.Stub {
@Override
public void resized(ClientWindowFrames frames, boolean reportDraw,
MergedConfiguration mergedConfiguration, InsetsState insetsState, boolean forceLayout,
- boolean alwaysConsumeSystemBars, int displayId, int seqId, boolean dragResizing) {
+ boolean alwaysConsumeSystemBars, int displayId, int seqId, int resizeMode) {
if (reportDraw) {
try {
mSession.finishDrawing(this, null /* postDrawTransaction */, seqId);
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index cc5de3ea1ec2..65f552278890 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1508,7 +1508,8 @@ public class LockPatternUtils {
STRONG_AUTH_REQUIRED_AFTER_LOCKOUT,
STRONG_AUTH_REQUIRED_AFTER_TIMEOUT,
STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN,
- STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT})
+ STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT,
+ SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED})
@Retention(RetentionPolicy.SOURCE)
public @interface StrongAuthFlags {}
@@ -1561,11 +1562,18 @@ public class LockPatternUtils {
public static final int STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT = 0x80;
/**
+ * Some authentication is required because the trustagent either timed out or was disabled
+ * manually.
+ */
+ public static final int SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED = 0x100;
+
+ /**
* Strong auth flags that do not prevent biometric methods from being accepted as auth.
* If any other flags are set, biometric authentication is disabled.
*/
private static final int ALLOWING_BIOMETRIC = STRONG_AUTH_NOT_REQUIRED
- | SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
+ | SOME_AUTH_REQUIRED_AFTER_USER_REQUEST
+ | SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED;
private final SparseIntArray mStrongAuthRequiredForUser = new SparseIntArray();
private final H mHandler;
diff --git a/core/jni/android_media_AudioFormat.h b/core/jni/android_media_AudioFormat.h
index 962b50147f3d..a9b19062b764 100644
--- a/core/jni/android_media_AudioFormat.h
+++ b/core/jni/android_media_AudioFormat.h
@@ -49,6 +49,7 @@
#define ENCODING_DRA 28
#define ENCODING_DTS_HD_MA 29
#define ENCODING_DTS_UHD_P2 30
+#define ENCODING_DSD 31
#define ENCODING_INVALID 0
#define ENCODING_DEFAULT 1
@@ -122,6 +123,8 @@ static inline audio_format_t audioFormatToNative(int audioFormat)
return AUDIO_FORMAT_DTS_HD_MA;
case ENCODING_DTS_UHD_P2:
return AUDIO_FORMAT_DTS_UHD_P2;
+ case ENCODING_DSD:
+ return AUDIO_FORMAT_DSD;
default:
return AUDIO_FORMAT_INVALID;
}
@@ -201,6 +204,8 @@ static inline int audioFormatFromNative(audio_format_t nativeFormat)
return ENCODING_DTS_UHD_P2;
case AUDIO_FORMAT_DEFAULT:
return ENCODING_DEFAULT;
+ case AUDIO_FORMAT_DSD:
+ return ENCODING_DSD;
default:
return ENCODING_INVALID;
}
diff --git a/core/jni/android_media_AudioProfile.h b/core/jni/android_media_AudioProfile.h
index 446bd6494f95..5096250e6bd3 100644
--- a/core/jni/android_media_AudioProfile.h
+++ b/core/jni/android_media_AudioProfile.h
@@ -25,6 +25,7 @@ namespace android {
// keep these values in sync with AudioProfile.java
#define ENCAPSULATION_TYPE_NONE 0
#define ENCAPSULATION_TYPE_IEC61937 1
+#define ENCAPSULATION_TYPE_PCM 2
static inline status_t audioEncapsulationTypeFromNative(
audio_encapsulation_type_t nEncapsulationType, int* encapsulationType) {
@@ -36,6 +37,9 @@ static inline status_t audioEncapsulationTypeFromNative(
case AUDIO_ENCAPSULATION_TYPE_IEC61937:
*encapsulationType = ENCAPSULATION_TYPE_IEC61937;
break;
+ case AUDIO_ENCAPSULATION_TYPE_PCM:
+ *encapsulationType = ENCAPSULATION_TYPE_PCM;
+ break;
default:
result = BAD_VALUE;
}
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 556636ddd210..e6f942e49565 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -596,6 +596,15 @@ message SecureSettingsProto {
optional SettingProto theme_customization_overlay_packages = 75 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto trust_agents_initialized = 57 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ message TrackpadGesture {
+ optional SettingProto trackpad_gesture_back_enabled = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto trackpad_gesture_home_enabled = 2 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto trackpad_gesture_overview_enabled = 3 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto trackpad_gesture_notification_enabled = 4 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto trackpad_gesture_quick_switch_enabled = 5 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ }
+ optional TrackpadGesture trackpad_gesture = 94;
+
message Tts {
option (android.msg_privacy).dest = DEST_EXPLICIT;
@@ -687,5 +696,5 @@ message SecureSettingsProto {
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 94;
+ // Next tag = 95;
}
diff --git a/services/core/jni/onload_settings.cpp b/core/proto/android/server/background_install_control.proto
index b21c34a9f9b9..38e6b4d21e2d 100644
--- a/services/core/jni/onload_settings.cpp
+++ b/core/proto/android/server/background_install_control.proto
@@ -14,26 +14,19 @@
* limitations under the License.
*/
-#include "jni.h"
-#include "utils/Log.h"
+syntax = "proto2";
+package com.android.server.pm;
-namespace android {
-int register_android_server_com_android_server_pm_Settings(JNIEnv* env);
-};
+option java_multiple_files = true;
-using namespace android;
-
-extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
- JNIEnv* env = NULL;
- jint result = -1;
-
- if (vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK) {
- ALOGE("GetEnv failed!");
- return result;
- }
- ALOG_ASSERT(env, "Could not retrieve the env!");
-
- register_android_server_com_android_server_pm_Settings(env);
+// Proto for the background installed packages.
+// It's used for serializing the background installed package info to disk.
+message BackgroundInstalledPackagesProto {
+ repeated BackgroundInstalledPackageProto bg_installed_pkg = 1;
+}
- return JNI_VERSION_1_4;
+// Proto for the background installed package entry
+message BackgroundInstalledPackageProto {
+ optional string package_name = 1;
+ optional int32 user_id = 2;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 2cf241f9b249..fd4d4f8a00f4 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4975,6 +4975,15 @@
<permission android:name="android.permission.ROTATE_SURFACE_FLINGER"
android:protectionLevel="signature|recents" />
+ <!-- @SystemApi Allows an application to provide hints to SurfaceFlinger that can influence
+ its wakes up time to compose the next frame. This is a subset of the capabilities granted
+ by {@link #ACCESS_SURFACE_FLINGER}.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.WAKEUP_SURFACE_FLINGER"
+ android:protectionLevel="signature|recents" />
+
<!-- Allows an application to take screen shots and more generally
get access to the frame buffer data.
<p>Not for use by third-party applications.
diff --git a/core/res/OWNERS b/core/res/OWNERS
index a2ef4005f5cb..b878189a9617 100644
--- a/core/res/OWNERS
+++ b/core/res/OWNERS
@@ -21,6 +21,11 @@ shanh@google.com
tsuji@google.com
yamasani@google.com
+# WindowManager team
+# TODO(262451702): Move WindowManager configs out of config.xml in a separate file
+per-file core/res/res/values/config.xml = file:/services/core/java/com/android/server/wm/OWNERS
+per-file core/res/res/values/symbols.xml = file:/services/core/java/com/android/server/wm/OWNERS
+
# Resources finalization
per-file res/xml/public-staging.xml = file:/tools/aapt2/OWNERS
per-file res/xml/public-final.xml = file:/tools/aapt2/OWNERS
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 2d832bc71684..c8b0601621ed 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -8987,6 +8987,10 @@
<!-- The service that provides {@link android.service.voice.HotwordDetectionService}.
@hide @SystemApi -->
<attr name="hotwordDetectionService" format="string" />
+ <!-- The service that provides {@link android.service.voice.VisualQueryDetectionService}.
+ @hide @SystemApi -->
+ <attr name="visualQueryDetectionService" format="string" />
+
</declare-styleable>
<!-- Use <code>game-service</code> as the root tag of the XML resource that
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 2fb766e46c5e..f995a6eeb5a3 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5281,6 +5281,10 @@
<!-- Whether using split screen aspect ratio as a default aspect ratio for unresizable apps. -->
<bool name="config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled">false</bool>
+ <!-- Whether the specific behaviour for translucent activities letterboxing is enabled.
+ TODO(b/255532890) Enable when ignoreOrientationRequest is set -->
+ <bool name="config_letterboxIsEnabledForTranslucentActivities">false</bool>
+
<!-- Whether a camera compat controller is enabled to allow the user to apply or revert
treatment for stretched issues in camera viewfinder. -->
<bool name="config_isCameraCompatControlForStretchedIssuesEnabled">false</bool>
diff --git a/core/res/res/values/locale_config.xml b/core/res/res/values/locale_config.xml
index 78ec14579bf1..bd93aa9402c3 100644
--- a/core/res/res/values/locale_config.xml
+++ b/core/res/res/values/locale_config.xml
@@ -23,7 +23,7 @@
<item>ak-GH</item> <!-- Akan (Ghana) -->
<item>am-ET</item> <!-- Amharic (Ethiopia) -->
<item>ar-AE</item> <!-- Arabic (United Arab Emirates) -->
- <item>ar-AE-u-nu-arab</item> <!-- Arabic (United Arab Emirates, Arabic Digits) -->
+ <item>ar-AE-u-nu-arab</item> <!-- Arabic (United Arab Emirates, Arabic-Indic Digits) -->
<item>ar-BH</item> <!-- Arabic (Bahrain) -->
<item>ar-BH-u-nu-latn</item> <!-- Arabic (Bahrain, Western Digits) -->
<item>ar-DJ</item> <!-- Arabic (Djibouti) -->
@@ -108,6 +108,7 @@
<item>cgg-UG</item> <!-- Chiga (Uganda) -->
<item>chr-US</item> <!-- Cherokee (United States) -->
<item>cs-CZ</item> <!-- Czech (Czechia) -->
+ <item>cv-RU</item> <!-- Chuvash (Russia) -->
<item>cy-GB</item> <!-- Welsh (United Kingdom) -->
<item>da-DK</item> <!-- Danish (Denmark) -->
<item>da-GL</item> <!-- Danish (Greenland) -->
@@ -270,42 +271,42 @@
<item>fa-AF-u-nu-latn</item> <!-- Persian (Afghanistan, Western Digits) -->
<item>fa-IR</item> <!-- Persian (Iran) -->
<item>fa-IR-u-nu-latn</item> <!-- Persian (Iran, Western Digits) -->
- <item>ff-Adlm-BF</item> <!-- Fulah (Adlam, Burkina Faso) -->
- <item>ff-Adlm-BF-u-nu-latn</item> <!-- Fulah (Adlam, Burkina Faso, Western Digits) -->
- <item>ff-Adlm-CM</item> <!-- Fulah (Adlam, Cameroon) -->
- <item>ff-Adlm-CM-u-nu-latn</item> <!-- Fulah (Adlam, Cameroon, Western Digits) -->
- <item>ff-Adlm-GH</item> <!-- Fulah (Adlam, Ghana) -->
- <item>ff-Adlm-GH-u-nu-latn</item> <!-- Fulah (Adlam, Ghana, Western Digits) -->
- <item>ff-Adlm-GM</item> <!-- Fulah (Adlam, Gambia) -->
- <item>ff-Adlm-GM-u-nu-latn</item> <!-- Fulah (Adlam, Gambia, Western Digits) -->
- <item>ff-Adlm-GN</item> <!-- Fulah (Adlam, Guinea) -->
- <item>ff-Adlm-GN-u-nu-latn</item> <!-- Fulah (Adlam, Guinea, Western Digits) -->
- <item>ff-Adlm-GW</item> <!-- Fulah (Adlam, Guinea-Bissau) -->
- <item>ff-Adlm-GW-u-nu-latn</item> <!-- Fulah (Adlam, Guinea-Bissau, Western Digits) -->
- <item>ff-Adlm-LR</item> <!-- Fulah (Adlam, Liberia) -->
- <item>ff-Adlm-LR-u-nu-latn</item> <!-- Fulah (Adlam, Liberia, Western Digits) -->
- <item>ff-Adlm-MR</item> <!-- Fulah (Adlam, Mauritania) -->
- <item>ff-Adlm-MR-u-nu-latn</item> <!-- Fulah (Adlam, Mauritania, Western Digits) -->
- <item>ff-Adlm-NE</item> <!-- Fulah (Adlam, Niger) -->
- <item>ff-Adlm-NE-u-nu-latn</item> <!-- Fulah (Adlam, Niger, Western Digits) -->
- <item>ff-Adlm-NG</item> <!-- Fulah (Adlam, Nigeria) -->
- <item>ff-Adlm-NG-u-nu-latn</item> <!-- Fulah (Adlam, Nigeria, Western Digits) -->
- <item>ff-Adlm-SL</item> <!-- Fulah (Adlam, Sierra Leone) -->
- <item>ff-Adlm-SL-u-nu-latn</item> <!-- Fulah (Adlam, Sierra Leone, Western Digits) -->
- <item>ff-Adlm-SN</item> <!-- Fulah (Adlam, Senegal) -->
- <item>ff-Adlm-SN-u-nu-latn</item> <!-- Fulah (Adlam, Senegal, Western Digits) -->
- <item>ff-Latn-BF</item> <!-- Fulah (Latin, Burkina Faso) -->
- <item>ff-Latn-CM</item> <!-- Fulah (Latin, Cameroon) -->
- <item>ff-Latn-GH</item> <!-- Fulah (Latin, Ghana) -->
- <item>ff-Latn-GM</item> <!-- Fulah (Latin, Gambia) -->
- <item>ff-Latn-GN</item> <!-- Fulah (Latin, Guinea) -->
- <item>ff-Latn-GW</item> <!-- Fulah (Latin, Guinea-Bissau) -->
- <item>ff-Latn-LR</item> <!-- Fulah (Latin, Liberia) -->
- <item>ff-Latn-MR</item> <!-- Fulah (Latin, Mauritania) -->
- <item>ff-Latn-NE</item> <!-- Fulah (Latin, Niger) -->
- <item>ff-Latn-NG</item> <!-- Fulah (Latin, Nigeria) -->
- <item>ff-Latn-SL</item> <!-- Fulah (Latin, Sierra Leone) -->
- <item>ff-Latn-SN</item> <!-- Fulah (Latin, Senegal) -->
+ <item>ff-Adlm-BF</item> <!-- Fula (Adlam, Burkina Faso) -->
+ <item>ff-Adlm-BF-u-nu-latn</item> <!-- Fula (Adlam, Burkina Faso, Western Digits) -->
+ <item>ff-Adlm-CM</item> <!-- Fula (Adlam, Cameroon) -->
+ <item>ff-Adlm-CM-u-nu-latn</item> <!-- Fula (Adlam, Cameroon, Western Digits) -->
+ <item>ff-Adlm-GH</item> <!-- Fula (Adlam, Ghana) -->
+ <item>ff-Adlm-GH-u-nu-latn</item> <!-- Fula (Adlam, Ghana, Western Digits) -->
+ <item>ff-Adlm-GM</item> <!-- Fula (Adlam, Gambia) -->
+ <item>ff-Adlm-GM-u-nu-latn</item> <!-- Fula (Adlam, Gambia, Western Digits) -->
+ <item>ff-Adlm-GN</item> <!-- Fula (Adlam, Guinea) -->
+ <item>ff-Adlm-GN-u-nu-latn</item> <!-- Fula (Adlam, Guinea, Western Digits) -->
+ <item>ff-Adlm-GW</item> <!-- Fula (Adlam, Guinea-Bissau) -->
+ <item>ff-Adlm-GW-u-nu-latn</item> <!-- Fula (Adlam, Guinea-Bissau, Western Digits) -->
+ <item>ff-Adlm-LR</item> <!-- Fula (Adlam, Liberia) -->
+ <item>ff-Adlm-LR-u-nu-latn</item> <!-- Fula (Adlam, Liberia, Western Digits) -->
+ <item>ff-Adlm-MR</item> <!-- Fula (Adlam, Mauritania) -->
+ <item>ff-Adlm-MR-u-nu-latn</item> <!-- Fula (Adlam, Mauritania, Western Digits) -->
+ <item>ff-Adlm-NE</item> <!-- Fula (Adlam, Niger) -->
+ <item>ff-Adlm-NE-u-nu-latn</item> <!-- Fula (Adlam, Niger, Western Digits) -->
+ <item>ff-Adlm-NG</item> <!-- Fula (Adlam, Nigeria) -->
+ <item>ff-Adlm-NG-u-nu-latn</item> <!-- Fula (Adlam, Nigeria, Western Digits) -->
+ <item>ff-Adlm-SL</item> <!-- Fula (Adlam, Sierra Leone) -->
+ <item>ff-Adlm-SL-u-nu-latn</item> <!-- Fula (Adlam, Sierra Leone, Western Digits) -->
+ <item>ff-Adlm-SN</item> <!-- Fula (Adlam, Senegal) -->
+ <item>ff-Adlm-SN-u-nu-latn</item> <!-- Fula (Adlam, Senegal, Western Digits) -->
+ <item>ff-Latn-BF</item> <!-- Fula (Latin, Burkina Faso) -->
+ <item>ff-Latn-CM</item> <!-- Fula (Latin, Cameroon) -->
+ <item>ff-Latn-GH</item> <!-- Fula (Latin, Ghana) -->
+ <item>ff-Latn-GM</item> <!-- Fula (Latin, Gambia) -->
+ <item>ff-Latn-GN</item> <!-- Fula (Latin, Guinea) -->
+ <item>ff-Latn-GW</item> <!-- Fula (Latin, Guinea-Bissau) -->
+ <item>ff-Latn-LR</item> <!-- Fula (Latin, Liberia) -->
+ <item>ff-Latn-MR</item> <!-- Fula (Latin, Mauritania) -->
+ <item>ff-Latn-NE</item> <!-- Fula (Latin, Niger) -->
+ <item>ff-Latn-NG</item> <!-- Fula (Latin, Nigeria) -->
+ <item>ff-Latn-SL</item> <!-- Fula (Latin, Sierra Leone) -->
+ <item>ff-Latn-SN</item> <!-- Fula (Latin, Senegal) -->
<item>fi-FI</item> <!-- Finnish (Finland) -->
<item>fil-PH</item> <!-- Filipino (Philippines) -->
<item>fo-DK</item> <!-- Faroese (Denmark) -->
@@ -373,12 +374,13 @@
<item>ha-NG</item> <!-- Hausa (Nigeria) -->
<item>haw-US</item> <!-- Hawaiian (United States) -->
<item>hi-IN</item> <!-- Hindi (India) -->
+ <item>hi-Latn-IN</item> <!-- Hindi (Latin, India) -->
<item>hr-BA</item> <!-- Croatian (Bosnia & Herzegovina) -->
<item>hr-HR</item> <!-- Croatian (Croatia) -->
<item>hsb-DE</item> <!-- Upper Sorbian (Germany) -->
<item>hu-HU</item> <!-- Hungarian (Hungary) -->
<item>hy-AM</item> <!-- Armenian (Armenia) -->
- <item>ia-001</item> <!-- Interlingua (World) -->
+ <item>ia-001</item> <!-- Interlingua (world) -->
<item>ig-NG</item> <!-- Igbo (Nigeria) -->
<item>ii-CN</item> <!-- Sichuan Yi (China) -->
<item>in-ID</item> <!-- Indonesian (Indonesia) -->
@@ -397,6 +399,7 @@
<item>kam-KE</item> <!-- Kamba (Kenya) -->
<item>kde-TZ</item> <!-- Makonde (Tanzania) -->
<item>kea-CV</item> <!-- Kabuverdianu (Cape Verde) -->
+ <item>kgp-BR</item> <!-- Kaingang (Brazil) -->
<item>khq-ML</item> <!-- Koyra Chiini (Mali) -->
<item>ki-KE</item> <!-- Kikuyu (Kenya) -->
<item>kk-KZ</item> <!-- Kazakh (Kazakhstan) -->
@@ -408,6 +411,9 @@
<item>ko-KP</item> <!-- Korean (North Korea) -->
<item>ko-KR</item> <!-- Korean (South Korea) -->
<item>kok-IN</item> <!-- Konkani (India) -->
+ <item>ks-Arab-IN</item> <!-- Kashmiri (Arabic, India) -->
+ <item>ks-Arab-IN-u-nu-latn</item> <!-- Kashmiri (Arabic, India, Western Digits) -->
+ <item>ks-Deva-IN</item> <!-- Kashmiri (Devanagari, India) -->
<item>ksb-TZ</item> <!-- Shambala (Tanzania) -->
<item>ksf-CM</item> <!-- Bafia (Cameroon) -->
<item>ksh-DE</item> <!-- Colognian (Germany) -->
@@ -435,7 +441,7 @@
<item>mg-MG</item> <!-- Malagasy (Madagascar) -->
<item>mgh-MZ</item> <!-- Makhuwa-Meetto (Mozambique) -->
<item>mgo-CM</item> <!-- Metaʼ (Cameroon) -->
- <item>mi-NZ</item> <!-- Maori (New Zealand) -->
+ <item>mi-NZ</item> <!-- Māori (New Zealand) -->
<item>mk-MK</item> <!-- Macedonian (North Macedonia) -->
<item>ml-IN</item> <!-- Malayalam (India) -->
<item>mn-MN</item> <!-- Mongolian (Mongolia) -->
@@ -500,6 +506,8 @@
<item>qu-BO</item> <!-- Quechua (Bolivia) -->
<item>qu-EC</item> <!-- Quechua (Ecuador) -->
<item>qu-PE</item> <!-- Quechua (Peru) -->
+ <item>raj-IN</item> <!-- Rajasthani (India) -->
+ <item>raj-IN-u-nu-latn</item> <!-- Rajasthani (India, Western Digits) -->
<item>rm-CH</item> <!-- Romansh (Switzerland) -->
<item>rn-BI</item> <!-- Rundi (Burundi) -->
<item>ro-MD</item> <!-- Romanian (Moldova) -->
@@ -514,11 +522,13 @@
<item>rw-RW</item> <!-- Kinyarwanda (Rwanda) -->
<item>rwk-TZ</item> <!-- Rwa (Tanzania) -->
<item>sa-IN</item> <!-- Sanskrit (India) -->
- <item>sah-RU</item> <!-- Sakha (Russia) -->
+ <item>sa-IN-u-nu-latn</item> <!-- Sanskrit (India, Western Digits) -->
+ <item>sah-RU</item> <!-- Yakut (Russia) -->
<item>saq-KE</item> <!-- Samburu (Kenya) -->
<item>sat-IN</item> <!-- Santali (India) -->
<item>sat-IN-u-nu-latn</item> <!-- Santali (India, Western Digits) -->
<item>sbp-TZ</item> <!-- Sangu (Tanzania) -->
+ <item>sc-IT</item> <!-- Sardinian (Italy) -->
<item>sd-Arab-PK</item> <!-- Sindhi (Arabic, Pakistan) -->
<item>sd-Arab-PK-u-nu-latn</item> <!-- Sindhi (Arabic, Pakistan, Western Digits) -->
<item>sd-Deva-IN</item> <!-- Sindhi (Devanagari, India) -->
@@ -565,6 +575,8 @@
<item>teo-UG</item> <!-- Teso (Uganda) -->
<item>tg-TJ</item> <!-- Tajik (Tajikistan) -->
<item>th-TH</item> <!-- Thai (Thailand) -->
+ <item>ti-ER</item> <!-- Tigrinya (Eritrea) -->
+ <item>ti-ET</item> <!-- Tigrinya (Ethiopia) -->
<item>tk-TM</item> <!-- Turkmen (Turkmenistan) -->
<item>to-TO</item> <!-- Tongan (Tonga) -->
<item>tr-CY</item> <!-- Turkish (Cyprus) -->
@@ -586,10 +598,14 @@
<item>vun-TZ</item> <!-- Vunjo (Tanzania) -->
<item>wae-CH</item> <!-- Walser (Switzerland) -->
<item>wo-SN</item> <!-- Wolof (Senegal) -->
+ <item>xh-ZA</item> <!-- Xhosa (South Africa) -->
<item>xog-UG</item> <!-- Soga (Uganda) -->
<item>yav-CM</item> <!-- Yangben (Cameroon) -->
<item>yo-BJ</item> <!-- Yoruba (Benin) -->
<item>yo-NG</item> <!-- Yoruba (Nigeria) -->
+ <item>yrl-BR</item> <!-- Nheengatu (Brazil) -->
+ <item>yrl-CO</item> <!-- Nheengatu (Colombia) -->
+ <item>yrl-VE</item> <!-- Nheengatu (Venezuela) -->
<item>yue-Hans-CN</item> <!-- Cantonese (Simplified, China) -->
<item>yue-Hant-HK</item> <!-- Cantonese (Traditional, Hong Kong) -->
<item>zgh-MA</item> <!-- Standard Moroccan Tamazight (Morocco) -->
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 90141e57ce8d..0aeee10caddf 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -118,6 +118,7 @@
<public name="enableTextStylingShortcuts" />
<public name="requiredDisplayCategory"/>
<public name="removed_maxConcurrentSessionsCount" />
+ <public name="visualQueryDetectionService" />
</staging-public-group>
<staging-public-group type="id" first-id="0x01cd0000">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index e8304d801ab2..151530bcccfb 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4397,6 +4397,9 @@
<!-- Set to true to make assistant show in front of the dream/screensaver. -->
<java-symbol type="bool" name="config_assistantOnTopOfDream"/>
+ <!-- Set to true to enable letterboxing on translucent activities. -->
+ <java-symbol type="bool" name="config_letterboxIsEnabledForTranslucentActivities" />
+
<java-symbol type="string" name="config_overrideComponentUiPackage" />
<java-symbol type="string" name="notification_channel_network_status" />
diff --git a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
index 4679a9ea6f66..0b7019995acb 100644
--- a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
@@ -19,6 +19,9 @@ package com.android.internal.util;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertFalse;
@@ -37,6 +40,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.UserInfo;
+import android.os.Looper;
import android.os.RemoteException;
import android.os.UserManager;
import android.provider.Settings;
@@ -233,6 +237,45 @@ public class LockPatternUtilsTest {
ComponentName.unflattenFromString("com.test/.TestAgent"));
}
+ @Test
+ public void isBiometricAllowedForUser_afterTrustagentExpired_returnsTrue()
+ throws RemoteException {
+ TestStrongAuthTracker tracker = createStrongAuthTracker();
+ tracker.changeStrongAuth(SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED);
+
+ assertTrue(tracker.isBiometricAllowedForUser(
+ /* isStrongBiometric = */ true,
+ DEMO_USER_ID));
+ }
+
+ @Test
+ public void isBiometricAllowedForUser_afterLockout_returnsFalse()
+ throws RemoteException {
+ TestStrongAuthTracker tracker = createStrongAuthTracker();
+ tracker.changeStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
+
+ assertFalse(tracker.isBiometricAllowedForUser(
+ /* isStrongBiometric = */ true,
+ DEMO_USER_ID));
+ }
+
+
+ private TestStrongAuthTracker createStrongAuthTracker() {
+ final Context context = new ContextWrapper(InstrumentationRegistry.getTargetContext());
+ return new TestStrongAuthTracker(context, Looper.getMainLooper());
+ }
+
+ private static class TestStrongAuthTracker extends LockPatternUtils.StrongAuthTracker {
+
+ TestStrongAuthTracker(Context context, Looper looper) {
+ super(context, looper);
+ }
+
+ public void changeStrongAuth(@StrongAuthFlags int strongAuthFlags) {
+ handleStrongAuthRequiredChanged(strongAuthFlags, DEMO_USER_ID);
+ }
+ }
+
private ILockSettings createTestLockSettings() {
final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
mLockPatternUtils = spy(new LockPatternUtils(context));
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index d4ac531b9c2d..1ab5e4bef2bb 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -169,6 +169,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-1944652783": {
+ "message": "Unable to tell MediaProjectionManagerService to stop the active projection: %s",
+ "level": "ERROR",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"-1941440781": {
"message": "Creating Pending Move-to-back: %s",
"level": "VERBOSE",
@@ -715,6 +721,12 @@
"group": "WM_DEBUG_ADD_REMOVE",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-1423223548": {
+ "message": "Unable to tell MediaProjectionManagerService about resizing the active projection: %s",
+ "level": "ERROR",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"-1421296808": {
"message": "Moving to RESUMED: %s (in existing)",
"level": "VERBOSE",
diff --git a/identity/java/android/security/identity/AuthenticationKeyMetadata.java b/identity/java/android/security/identity/AuthenticationKeyMetadata.java
index d4c28f8d459a..c6abc22c305f 100644
--- a/identity/java/android/security/identity/AuthenticationKeyMetadata.java
+++ b/identity/java/android/security/identity/AuthenticationKeyMetadata.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 The Android Open Source Project
+ * Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,11 +24,11 @@ import java.time.Instant;
/**
* Data about authentication keys.
*/
-public class AuthenticationKeyMetadata {
+public final class AuthenticationKeyMetadata {
private int mUsageCount;
private Instant mExpirationDate;
- AuthenticationKeyMetadata(int usageCount, Instant expirationDate) {
+ AuthenticationKeyMetadata(int usageCount, @NonNull Instant expirationDate) {
mUsageCount = usageCount;
mExpirationDate = expirationDate;
}
diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml
index 8881be7326a9..36d3313a9f3b 100644
--- a/libs/WindowManager/Shell/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/AndroidManifest.xml
@@ -21,5 +21,6 @@
<uses-permission android:name="android.permission.CAPTURE_BLACKOUT_CONTENT" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="android.permission.ROTATE_SURFACE_FLINGER" />
+ <uses-permission android:name="android.permission.WAKEUP_SURFACE_FLINGER" />
<uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
</manifest>
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
index 70755e6cc3cf..dcce4698c252 100644
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
@@ -44,67 +44,15 @@
android:background="@color/tv_pip_menu_dim_layer"
android:alpha="0"/>
- <ScrollView
- android:id="@+id/tv_pip_menu_scroll"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:scrollbars="none"
- android:visibility="gone"/>
-
- <HorizontalScrollView
- android:id="@+id/tv_pip_menu_horizontal_scroll"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:scrollbars="none">
-
- <LinearLayout
- android:id="@+id/tv_pip_menu_action_buttons"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:alpha="0">
-
- <Space
- android:layout_width="@dimen/pip_menu_button_wrapper_margin"
- android:layout_height="@dimen/pip_menu_button_wrapper_margin"/>
-
- <com.android.wm.shell.common.TvWindowMenuActionButton
- android:id="@+id/tv_pip_menu_fullscreen_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/pip_ic_fullscreen_white"
- android:text="@string/pip_fullscreen" />
-
- <com.android.wm.shell.common.TvWindowMenuActionButton
- android:id="@+id/tv_pip_menu_close_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/pip_ic_close_white"
- android:text="@string/pip_close" />
-
- <!-- More TvWindowMenuActionButtons may be added here at runtime. -->
-
- <com.android.wm.shell.common.TvWindowMenuActionButton
- android:id="@+id/tv_pip_menu_move_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/pip_ic_move_white"
- android:text="@string/pip_move" />
-
- <com.android.wm.shell.common.TvWindowMenuActionButton
- android:id="@+id/tv_pip_menu_expand_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/pip_ic_collapse"
- android:visibility="gone"
- android:text="@string/pip_collapse" />
-
- <Space
- android:layout_width="@dimen/pip_menu_button_wrapper_margin"
- android:layout_height="@dimen/pip_menu_button_wrapper_margin"/>
-
- </LinearLayout>
- </HorizontalScrollView>
+ <com.android.internal.widget.RecyclerView
+ android:id="@+id/tv_pip_menu_action_buttons"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:padding="@dimen/pip_menu_button_start_end_offset"
+ android:clipToPadding="false"
+ android:alpha="0"
+ android:contentDescription="@string/a11y_pip_menu_entered"/>
</FrameLayout>
<!-- Frame around the content, just overlapping the corners to make them round -->
diff --git a/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml b/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml
index c4dbd39c729a..b2ac85b018be 100644
--- a/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml
@@ -21,6 +21,7 @@
android:layout_width="@dimen/tv_window_menu_button_size"
android:layout_height="@dimen/tv_window_menu_button_size"
android:padding="@dimen/tv_window_menu_button_margin"
+ android:duplicateParentState="true"
android:stateListAnimator="@animator/tv_window_menu_action_button_animator"
android:focusable="true">
diff --git a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
index 9833a88a1c0a..0b61d7a85d9e 100644
--- a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
+++ b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
@@ -28,7 +28,7 @@
<dimen name="pip_menu_background_corner_radius">6dp</dimen>
<dimen name="pip_menu_border_width">4dp</dimen>
<dimen name="pip_menu_outer_space">24dp</dimen>
- <dimen name="pip_menu_button_wrapper_margin">26dp</dimen>
+ <dimen name="pip_menu_button_start_end_offset">30dp</dimen>
<!-- outer space minus border width -->
<dimen name="pip_menu_outer_space_frame">20dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
index 94aeb2beb1e0..af13bf54f691 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
@@ -346,7 +346,7 @@ public class SystemWindows {
public void resized(ClientWindowFrames frames, boolean reportDraw,
MergedConfiguration newMergedConfiguration, InsetsState insetsState,
boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId,
- boolean dragResizing) {}
+ int resizeMode) {}
@Override
public void insetsControlChanged(InsetsState insetsState,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
index 39b0b5500cea..8ba785a1f03a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
@@ -19,6 +19,8 @@ package com.android.wm.shell.common;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.Handler;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -30,11 +32,11 @@ import com.android.wm.shell.R;
/**
* A common action button for TV window menu layouts.
*/
-public class TvWindowMenuActionButton extends RelativeLayout implements View.OnClickListener {
+public class TvWindowMenuActionButton extends RelativeLayout {
private final ImageView mIconImageView;
private final View mButtonBackgroundView;
- private final View mButtonView;
- private OnClickListener mOnClickListener;
+
+ private Icon mCurrentIcon;
public TvWindowMenuActionButton(Context context) {
this(context, null, 0, 0);
@@ -56,7 +58,6 @@ public class TvWindowMenuActionButton extends RelativeLayout implements View.OnC
inflater.inflate(R.layout.tv_window_menu_action_button, this);
mIconImageView = findViewById(R.id.icon);
- mButtonView = findViewById(R.id.button);
mButtonBackgroundView = findViewById(R.id.background);
final int[] values = new int[]{android.R.attr.src, android.R.attr.text};
@@ -71,23 +72,6 @@ public class TvWindowMenuActionButton extends RelativeLayout implements View.OnC
typedArray.recycle();
}
- @Override
- public void setOnClickListener(OnClickListener listener) {
- // We do not want to set an OnClickListener to the TvWindowMenuActionButton itself, but only
- // to the ImageView. So let's "cash" the listener we've been passed here and set a "proxy"
- // listener to the ImageView.
- mOnClickListener = listener;
- mButtonView.setOnClickListener(listener != null ? this : null);
- }
-
- @Override
- public void onClick(View v) {
- if (mOnClickListener != null) {
- // Pass the correct view - this.
- mOnClickListener.onClick(this);
- }
- }
-
/**
* Sets the drawable for the button with the given drawable.
*/
@@ -104,11 +88,24 @@ public class TvWindowMenuActionButton extends RelativeLayout implements View.OnC
}
}
+ public void setImageIconAsync(Icon icon, Handler handler) {
+ mCurrentIcon = icon;
+ // Remove old image while waiting for the new one to load.
+ mIconImageView.setImageDrawable(null);
+ icon.loadDrawableAsync(mContext, d -> {
+ // The image hasn't been set any other way and the drawable belongs to the most
+ // recently set Icon.
+ if (mIconImageView.getDrawable() == null && mCurrentIcon == icon) {
+ mIconImageView.setImageDrawable(d);
+ }
+ }, handler);
+ }
+
/**
* Sets the text for description the with the given string.
*/
public void setTextAndDescription(CharSequence text) {
- mButtonView.setContentDescription(text);
+ setContentDescription(text);
}
/**
@@ -118,16 +115,6 @@ public class TvWindowMenuActionButton extends RelativeLayout implements View.OnC
setTextAndDescription(getContext().getString(resId));
}
- @Override
- public void setEnabled(boolean enabled) {
- mButtonView.setEnabled(enabled);
- }
-
- @Override
- public boolean isEnabled() {
- return mButtonView.isEnabled();
- }
-
/**
* Marks this button as a custom close action button.
* This changes the style of the action button to highlight that this action finishes the
@@ -147,10 +134,10 @@ public class TvWindowMenuActionButton extends RelativeLayout implements View.OnC
@Override
public String toString() {
- if (mButtonView.getContentDescription() == null) {
+ if (getContentDescription() == null) {
return TvWindowMenuActionButton.class.getSimpleName();
}
- return mButtonView.getContentDescription().toString();
+ return getContentDescription().toString();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
index 8022e9b1cd81..b144d22fc3ee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
@@ -81,6 +81,7 @@ public abstract class TvPipModule {
PipParamsChangedForwarder pipParamsChangedForwarder,
DisplayController displayController,
WindowManagerShellWrapper windowManagerShellWrapper,
+ @ShellMainThread Handler mainHandler, // needed for registerReceiverForAllUsers()
@ShellMainThread ShellExecutor mainExecutor) {
return Optional.of(
TvPipController.create(
@@ -100,6 +101,7 @@ public abstract class TvPipModule {
pipParamsChangedForwarder,
displayController,
windowManagerShellWrapper,
+ mainHandler,
mainExecutor));
}
@@ -157,22 +159,17 @@ public abstract class TvPipModule {
Context context,
TvPipBoundsState tvPipBoundsState,
SystemWindows systemWindows,
- PipMediaController pipMediaController,
@ShellMainThread Handler mainHandler) {
- return new TvPipMenuController(context, tvPipBoundsState, systemWindows, pipMediaController,
- mainHandler);
+ return new TvPipMenuController(context, tvPipBoundsState, systemWindows, mainHandler);
}
- // Handler needed for registerReceiverForAllUsers()
@WMSingleton
@Provides
static TvPipNotificationController provideTvPipNotificationController(Context context,
PipMediaController pipMediaController,
- PipParamsChangedForwarder pipParamsChangedForwarder,
- TvPipBoundsState tvPipBoundsState,
- @ShellMainThread Handler mainHandler) {
+ PipParamsChangedForwarder pipParamsChangedForwarder) {
return new TvPipNotificationController(context, pipMediaController,
- pipParamsChangedForwarder, tvPipBoundsState, mainHandler);
+ pipParamsChangedForwarder);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java
new file mode 100644
index 000000000000..222307fba8c2
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip.tv;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.os.Handler;
+
+import com.android.wm.shell.common.TvWindowMenuActionButton;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+abstract class TvPipAction {
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"ACTION_"}, value = {
+ ACTION_FULLSCREEN,
+ ACTION_CLOSE,
+ ACTION_MOVE,
+ ACTION_EXPAND_COLLAPSE,
+ ACTION_CUSTOM,
+ ACTION_CUSTOM_CLOSE
+ })
+ public @interface ActionType {
+ }
+
+ public static final int ACTION_FULLSCREEN = 0;
+ public static final int ACTION_CLOSE = 1;
+ public static final int ACTION_MOVE = 2;
+ public static final int ACTION_EXPAND_COLLAPSE = 3;
+ public static final int ACTION_CUSTOM = 4;
+ public static final int ACTION_CUSTOM_CLOSE = 5;
+
+ @ActionType
+ private final int mActionType;
+
+ @NonNull
+ private final SystemActionsHandler mSystemActionsHandler;
+
+ TvPipAction(@ActionType int actionType, @NonNull SystemActionsHandler systemActionsHandler) {
+ Objects.requireNonNull(systemActionsHandler);
+ mActionType = actionType;
+ mSystemActionsHandler = systemActionsHandler;
+ }
+
+ boolean isCloseAction() {
+ return mActionType == ACTION_CLOSE || mActionType == ACTION_CUSTOM_CLOSE;
+ }
+
+ @ActionType
+ int getActionType() {
+ return mActionType;
+ }
+
+ abstract void populateButton(@NonNull TvWindowMenuActionButton button, Handler mainHandler);
+
+ abstract PendingIntent getPendingIntent();
+
+ void executeAction() {
+ mSystemActionsHandler.executeAction(mActionType);
+ }
+
+ abstract Notification.Action toNotificationAction(Context context);
+
+ interface SystemActionsHandler {
+ void executeAction(@TvPipAction.ActionType int actionType);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java
new file mode 100644
index 000000000000..fa62a73ca9b4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip.tv;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CLOSE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CUSTOM;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CUSTOM_CLOSE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_EXPAND_COLLAPSE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_FULLSCREEN;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_MOVE;
+import static com.android.wm.shell.pip.tv.TvPipController.ACTION_CLOSE_PIP;
+import static com.android.wm.shell.pip.tv.TvPipController.ACTION_MOVE_PIP;
+import static com.android.wm.shell.pip.tv.TvPipController.ACTION_TOGGLE_EXPANDED_PIP;
+import static com.android.wm.shell.pip.tv.TvPipController.ACTION_TO_FULLSCREEN;
+
+import android.annotation.NonNull;
+import android.app.RemoteAction;
+import android.content.Context;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.R;
+import com.android.wm.shell.pip.PipMediaController;
+import com.android.wm.shell.pip.PipUtils;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Creates the system TvPipActions (fullscreen, close, move, expand/collapse), and handles all the
+ * changes to the actions, including the custom app actions and media actions. Other components can
+ * listen to those changes.
+ */
+public class TvPipActionsProvider implements TvPipAction.SystemActionsHandler {
+ private static final String TAG = TvPipActionsProvider.class.getSimpleName();
+
+ private static final int CLOSE_ACTION_INDEX = 1;
+ private static final int FIRST_CUSTOM_ACTION_INDEX = 2;
+
+ private final List<Listener> mListeners = new ArrayList<>();
+ private final TvPipAction.SystemActionsHandler mSystemActionsHandler;
+
+ private final List<TvPipAction> mActionsList;
+ private final TvPipSystemAction mDefaultCloseAction;
+ private final TvPipSystemAction mExpandCollapseAction;
+
+ private final List<RemoteAction> mMediaActions = new ArrayList<>();
+ private final List<RemoteAction> mAppActions = new ArrayList<>();
+
+ public TvPipActionsProvider(Context context, PipMediaController pipMediaController,
+ TvPipAction.SystemActionsHandler systemActionsHandler) {
+ mSystemActionsHandler = systemActionsHandler;
+
+ mActionsList = new ArrayList<>();
+ mActionsList.add(new TvPipSystemAction(ACTION_FULLSCREEN, R.string.pip_fullscreen,
+ R.drawable.pip_ic_fullscreen_white, ACTION_TO_FULLSCREEN, context,
+ mSystemActionsHandler));
+
+ mDefaultCloseAction = new TvPipSystemAction(ACTION_CLOSE, R.string.pip_close,
+ R.drawable.pip_ic_close_white, ACTION_CLOSE_PIP, context, mSystemActionsHandler);
+ mActionsList.add(mDefaultCloseAction);
+
+ mActionsList.add(new TvPipSystemAction(ACTION_MOVE, R.string.pip_move,
+ R.drawable.pip_ic_move_white, ACTION_MOVE_PIP, context, mSystemActionsHandler));
+
+ mExpandCollapseAction = new TvPipSystemAction(ACTION_EXPAND_COLLAPSE, R.string.pip_collapse,
+ R.drawable.pip_ic_collapse, ACTION_TOGGLE_EXPANDED_PIP, context,
+ mSystemActionsHandler);
+ mActionsList.add(mExpandCollapseAction);
+
+ pipMediaController.addActionListener(this::onMediaActionsChanged);
+ }
+
+ @Override
+ public void executeAction(@TvPipAction.ActionType int actionType) {
+ if (mSystemActionsHandler != null) {
+ mSystemActionsHandler.executeAction(actionType);
+ }
+ }
+
+ private void notifyActionsChanged(int added, int changed, int startIndex) {
+ for (Listener listener : mListeners) {
+ listener.onActionsChanged(added, changed, startIndex);
+ }
+ }
+
+ @VisibleForTesting(visibility = PACKAGE)
+ public void setAppActions(@NonNull List<RemoteAction> appActions, RemoteAction closeAction) {
+ // Update close action.
+ mActionsList.set(CLOSE_ACTION_INDEX,
+ closeAction == null ? mDefaultCloseAction
+ : new TvPipCustomAction(ACTION_CUSTOM_CLOSE, closeAction,
+ mSystemActionsHandler));
+ notifyActionsChanged(/* added= */ 0, /* updated= */ 1, CLOSE_ACTION_INDEX);
+
+ // Replace custom actions with new ones.
+ mAppActions.clear();
+ for (RemoteAction action : appActions) {
+ if (action != null && !PipUtils.remoteActionsMatch(action, closeAction)) {
+ // Only show actions that aren't duplicates of the custom close action.
+ mAppActions.add(action);
+ }
+ }
+
+ updateCustomActions(mAppActions);
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public void onMediaActionsChanged(List<RemoteAction> actions) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onMediaActionsChanged()", TAG);
+
+ mMediaActions.clear();
+ // Don't show disabled actions.
+ for (RemoteAction remoteAction : actions) {
+ if (remoteAction.isEnabled()) {
+ mMediaActions.add(remoteAction);
+ }
+ }
+
+ updateCustomActions(mMediaActions);
+ }
+
+ private void updateCustomActions(@NonNull List<RemoteAction> customActions) {
+ List<RemoteAction> newCustomActions = customActions;
+ if (newCustomActions == mMediaActions && !mAppActions.isEmpty()) {
+ // Don't show the media actions while there are app actions.
+ return;
+ } else if (newCustomActions == mAppActions && mAppActions.isEmpty()) {
+ // If all the app actions were removed, show the media actions.
+ newCustomActions = mMediaActions;
+ }
+
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: replaceCustomActions, count: %d", TAG, newCustomActions.size());
+ int oldCustomActionsCount = 0;
+ for (TvPipAction action : mActionsList) {
+ if (action.getActionType() == ACTION_CUSTOM) {
+ oldCustomActionsCount++;
+ }
+ }
+ mActionsList.removeIf(tvPipAction -> tvPipAction.getActionType() == ACTION_CUSTOM);
+
+ List<TvPipAction> actions = new ArrayList<>();
+ for (RemoteAction action : newCustomActions) {
+ actions.add(new TvPipCustomAction(ACTION_CUSTOM, action, mSystemActionsHandler));
+ }
+ mActionsList.addAll(FIRST_CUSTOM_ACTION_INDEX, actions);
+
+ int added = newCustomActions.size() - oldCustomActionsCount;
+ int changed = Math.min(newCustomActions.size(), oldCustomActionsCount);
+ notifyActionsChanged(added, changed, FIRST_CUSTOM_ACTION_INDEX);
+ }
+
+ @VisibleForTesting(visibility = PACKAGE)
+ public void updateExpansionEnabled(boolean enabled) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: updateExpansionState, enabled: %b", TAG, enabled);
+ int actionIndex = mActionsList.indexOf(mExpandCollapseAction);
+ boolean actionInList = actionIndex != -1;
+ if (enabled && !actionInList) {
+ mActionsList.add(mExpandCollapseAction);
+ actionIndex = mActionsList.size() - 1;
+ } else if (!enabled && actionInList) {
+ mActionsList.remove(actionIndex);
+ } else {
+ return;
+ }
+ notifyActionsChanged(/* added= */ enabled ? 1 : -1, /* updated= */ 0, actionIndex);
+ }
+
+ @VisibleForTesting(visibility = PACKAGE)
+ public void onPipExpansionToggled(boolean expanded) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onPipExpansionToggled, expanded: %b", TAG, expanded);
+
+ mExpandCollapseAction.update(
+ expanded ? R.string.pip_collapse : R.string.pip_expand,
+ expanded ? R.drawable.pip_ic_collapse : R.drawable.pip_ic_expand);
+
+ notifyActionsChanged(/* added= */ 0, /* updated= */ 1,
+ mActionsList.indexOf(mExpandCollapseAction));
+ }
+
+ List<TvPipAction> getActionsList() {
+ return mActionsList;
+ }
+
+ @NonNull
+ TvPipAction getCloseAction() {
+ return mActionsList.get(CLOSE_ACTION_INDEX);
+ }
+
+ void addListener(Listener listener) {
+ if (!mListeners.contains(listener)) {
+ mListeners.add(listener);
+ }
+ }
+
+ /**
+ * Returns the index of the first action of the given action type or -1 if none can be found.
+ */
+ int getFirstIndexOfAction(@TvPipAction.ActionType int actionType) {
+ for (int i = 0; i < mActionsList.size(); i++) {
+ if (mActionsList.get(i).getActionType() == actionType) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Allow components to listen to updates to the actions list, including where they happen so
+ * that changes can be animated.
+ */
+ interface Listener {
+ /**
+ * Notifies the listener how many actions were added/removed or updated.
+ *
+ * @param added can be positive (number of actions added), negative (number of actions
+ * removed) or zero (the number of actions stayed the same).
+ * @param updated the number of actions that might have been updated and need to be
+ * refreshed.
+ * @param startIndex The index of the first updated action. The added/removed actions start
+ * at (startIndex + updated).
+ */
+ void onActionsChanged(int added, int updated, int startIndex);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 3e8de454bcff..76710818f8e5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -22,13 +22,16 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import android.annotation.IntDef;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
-import android.app.PendingIntent;
import android.app.RemoteAction;
import android.app.TaskInfo;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
+import android.os.Handler;
import android.os.RemoteException;
import android.view.Gravity;
@@ -67,8 +70,8 @@ import java.util.Set;
*/
public class TvPipController implements PipTransitionController.PipTransitionCallback,
TvPipBoundsController.PipBoundsListener, TvPipMenuController.Delegate,
- TvPipNotificationController.Delegate, DisplayController.OnDisplaysChangedListener,
- ConfigurationChangeListener, UserChangeListener {
+ DisplayController.OnDisplaysChangedListener, ConfigurationChangeListener,
+ UserChangeListener {
private static final String TAG = "TvPipController";
private static final int NONEXISTENT_TASK_ID = -1;
@@ -98,6 +101,17 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
*/
private static final int STATE_PIP_MENU = 2;
+ static final String ACTION_SHOW_PIP_MENU =
+ "com.android.wm.shell.pip.tv.notification.action.SHOW_PIP_MENU";
+ static final String ACTION_CLOSE_PIP =
+ "com.android.wm.shell.pip.tv.notification.action.CLOSE_PIP";
+ static final String ACTION_MOVE_PIP =
+ "com.android.wm.shell.pip.tv.notification.action.MOVE_PIP";
+ static final String ACTION_TOGGLE_EXPANDED_PIP =
+ "com.android.wm.shell.pip.tv.notification.action.TOGGLE_EXPANDED_PIP";
+ static final String ACTION_TO_FULLSCREEN =
+ "com.android.wm.shell.pip.tv.notification.action.FULLSCREEN";
+
private final Context mContext;
private final ShellController mShellController;
@@ -107,6 +121,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
private final PipAppOpsListener mAppOpsListener;
private final PipTaskOrganizer mPipTaskOrganizer;
private final PipMediaController mPipMediaController;
+ private final TvPipActionsProvider mTvPipActionsProvider;
private final TvPipNotificationController mPipNotificationController;
private final TvPipMenuController mTvPipMenuController;
private final PipTransitionController mPipTransitionController;
@@ -115,14 +130,16 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
private final DisplayController mDisplayController;
private final WindowManagerShellWrapper mWmShellWrapper;
private final ShellExecutor mMainExecutor;
+ private final Handler mMainHandler; // For registering the broadcast receiver
private final TvPipImpl mImpl = new TvPipImpl();
+ private final ActionBroadcastReceiver mActionBroadcastReceiver;
+
@State
private int mState = STATE_NO_PIP;
private int mPreviousGravity = TvPipBoundsState.DEFAULT_TV_GRAVITY;
private int mPinnedTaskId = NONEXISTENT_TASK_ID;
- private RemoteAction mCloseAction;
// How long the shell will wait for the app to close the PiP if a custom action is set.
private int mPipForceCloseDelay;
@@ -146,6 +163,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
PipParamsChangedForwarder pipParamsChangedForwarder,
DisplayController displayController,
WindowManagerShellWrapper wmShell,
+ Handler mainHandler,
ShellExecutor mainExecutor) {
return new TvPipController(
context,
@@ -164,6 +182,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
pipParamsChangedForwarder,
displayController,
wmShell,
+ mainHandler,
mainExecutor).mImpl;
}
@@ -184,8 +203,10 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
PipParamsChangedForwarder pipParamsChangedForwarder,
DisplayController displayController,
WindowManagerShellWrapper wmShellWrapper,
+ Handler mainHandler,
ShellExecutor mainExecutor) {
mContext = context;
+ mMainHandler = mainHandler;
mMainExecutor = mainExecutor;
mShellController = shellController;
mDisplayController = displayController;
@@ -198,12 +219,17 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
mTvPipBoundsController.setListener(this);
mPipMediaController = pipMediaController;
+ mTvPipActionsProvider = new TvPipActionsProvider(context, pipMediaController,
+ this::executeAction);
mPipNotificationController = pipNotificationController;
- mPipNotificationController.setDelegate(this);
+ mPipNotificationController.setTvPipActionsProvider(mTvPipActionsProvider);
mTvPipMenuController = tvPipMenuController;
mTvPipMenuController.setDelegate(this);
+ mTvPipMenuController.setTvPipActionsProvider(mTvPipActionsProvider);
+
+ mActionBroadcastReceiver = new ActionBroadcastReceiver();
mAppOpsListener = pipAppOpsListener;
mPipTaskOrganizer = pipTaskOrganizer;
@@ -241,7 +267,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
"%s: onConfigurationChanged(), state=%s", TAG, stateToName(mState));
loadConfigurations();
- mPipNotificationController.onConfigurationChanged(mContext);
+ mPipNotificationController.onConfigurationChanged();
mTvPipBoundsAlgorithm.onConfigurationChanged(mContext);
}
@@ -256,9 +282,10 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
* Starts the process if bringing up the Pip menu if by issuing a command to move Pip
* task/window to the "Menu" position. We'll show the actual Menu UI (eg. actions) once the Pip
* task/window is properly positioned in {@link #onPipTransitionFinished(int)}.
+ *
+ * @param moveMenu If true, show the moveMenu, otherwise show the regular menu.
*/
- @Override
- public void showPictureInPictureMenu() {
+ private void showPictureInPictureMenu(boolean moveMenu) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: showPictureInPictureMenu(), state=%s", TAG, stateToName(mState));
@@ -269,7 +296,11 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
}
setState(STATE_PIP_MENU);
- mTvPipMenuController.showMenu();
+ if (moveMenu) {
+ mTvPipMenuController.showMovementMenu();
+ } else {
+ mTvPipMenuController.showMenu();
+ }
updatePinnedStackBounds();
}
@@ -289,8 +320,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
/**
* Opens the "Pip-ed" Activity fullscreen.
*/
- @Override
- public void movePipToFullscreen() {
+ private void movePipToFullscreen() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: movePipToFullscreen(), state=%s", TAG, stateToName(mState));
@@ -298,8 +328,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
onPipDisappeared();
}
- @Override
- public void togglePipExpansion() {
+ private void togglePipExpansion() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: togglePipExpansion()", TAG);
boolean expanding = !mTvPipBoundsState.isTvPipExpanded();
@@ -310,18 +339,11 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
}
mTvPipBoundsState.setTvPipManuallyCollapsed(!expanding);
mTvPipBoundsState.setTvPipExpanded(expanding);
- mPipNotificationController.updateExpansionState();
updatePinnedStackBounds();
}
@Override
- public void enterPipMovementMenu() {
- setState(STATE_PIP_MENU);
- mTvPipMenuController.showMovementMenuOnly();
- }
-
- @Override
public void movePip(int keycode) {
if (mTvPipBoundsAlgorithm.updateGravity(keycode)) {
mTvPipMenuController.updateGravity(mTvPipBoundsState.getTvPipGravity());
@@ -373,30 +395,23 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
@Override
public void onPipTargetBoundsChange(Rect targetBounds, int animationDuration) {
mPipTaskOrganizer.scheduleAnimateResizePip(targetBounds,
- animationDuration, rect -> mTvPipMenuController.updateExpansionState());
+ animationDuration, null);
mTvPipMenuController.onPipTransitionToTargetBoundsStarted(targetBounds);
}
/**
* Closes Pip window.
*/
- @Override
public void closePip() {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: closePip(), state=%s, loseAction=%s", TAG, stateToName(mState),
- mCloseAction);
-
- if (mCloseAction != null) {
- try {
- mCloseAction.getActionIntent().send();
- } catch (PendingIntent.CanceledException e) {
- ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: Failed to send close action, %s", TAG, e);
- }
- mMainExecutor.executeDelayed(() -> closeCurrentPiP(mPinnedTaskId), mPipForceCloseDelay);
- } else {
- closeCurrentPiP(mPinnedTaskId);
- }
+ closeCurrentPiP(mPinnedTaskId);
+ }
+
+ /**
+ * Force close the current PiP after some time in case the custom action hasn't done it by
+ * itself.
+ */
+ public void customClosePip() {
+ mMainExecutor.executeDelayed(() -> closeCurrentPiP(mPinnedTaskId), mPipForceCloseDelay);
}
private void closeCurrentPiP(int pinnedTaskId) {
@@ -426,6 +441,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
mPinnedTaskId = pinnedTask.taskId;
mPipMediaController.onActivityPinned();
+ mActionBroadcastReceiver.register();
mPipNotificationController.show(pinnedTask.topActivity.getPackageName());
}
@@ -445,6 +461,8 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
"%s: onPipDisappeared() state=%s", TAG, stateToName(mState));
mPipNotificationController.dismiss();
+ mActionBroadcastReceiver.unregister();
+
mTvPipMenuController.closeMenu();
mTvPipBoundsState.resetTvPipState();
mTvPipBoundsController.onPipDismissed();
@@ -454,6 +472,11 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
@Override
public void onPipTransitionStarted(int direction, Rect currentPipBounds) {
+ final boolean enterPipTransition = PipAnimationController.isInPipDirection(direction);
+ if (enterPipTransition && mState == STATE_NO_PIP) {
+ // Set the initial ability to expand the PiP when entering PiP.
+ updateExpansionState();
+ }
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: onPipTransition_Started(), state=%s, direction=%d",
TAG, stateToName(mState), direction);
@@ -465,6 +488,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
"%s: onPipTransition_Canceled(), state=%s", TAG, stateToName(mState));
mTvPipMenuController.onPipTransitionFinished(
PipAnimationController.isInPipDirection(direction));
+ mTvPipActionsProvider.onPipExpansionToggled(mTvPipBoundsState.isTvPipExpanded());
}
@Override
@@ -477,6 +501,12 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
"%s: onPipTransition_Finished(), state=%s, direction=%d",
TAG, stateToName(mState), direction);
mTvPipMenuController.onPipTransitionFinished(enterPipTransition);
+ mTvPipActionsProvider.onPipExpansionToggled(mTvPipBoundsState.isTvPipExpanded());
+ }
+
+ private void updateExpansionState() {
+ mTvPipActionsProvider.updateExpansionEnabled(mTvPipBoundsState.isTvExpandedPipSupported()
+ && mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0);
}
private void setState(@State int state) {
@@ -534,8 +564,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: onActionsChanged()", TAG);
- mTvPipMenuController.setAppActions(actions, closeAction);
- mCloseAction = closeAction;
+ mTvPipActionsProvider.setAppActions(actions, closeAction);
}
@Override
@@ -555,7 +584,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
"%s: onExpandedAspectRatioChanged: %f", TAG, ratio);
mTvPipBoundsState.setDesiredTvExpandedAspectRatio(ratio, false);
- mTvPipMenuController.updateExpansionState();
+ updateExpansionState();
// 1) PiP is expanded and only aspect ratio changed, but wasn't disabled
// --> update bounds, but don't toggle
@@ -662,6 +691,90 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
}
}
+ private void executeAction(@TvPipAction.ActionType int actionType) {
+ switch (actionType) {
+ case TvPipAction.ACTION_FULLSCREEN:
+ movePipToFullscreen();
+ break;
+ case TvPipAction.ACTION_CLOSE:
+ closePip();
+ break;
+ case TvPipAction.ACTION_MOVE:
+ showPictureInPictureMenu(/* moveMenu= */ true);
+ break;
+ case TvPipAction.ACTION_CUSTOM_CLOSE:
+ customClosePip();
+ break;
+ case TvPipAction.ACTION_EXPAND_COLLAPSE:
+ togglePipExpansion();
+ break;
+ default:
+ // NOOP
+ break;
+ }
+ }
+
+ private class ActionBroadcastReceiver extends BroadcastReceiver {
+ private static final String SYSTEMUI_PERMISSION = "com.android.systemui.permission.SELF";
+
+ final IntentFilter mIntentFilter;
+
+ {
+ mIntentFilter = new IntentFilter();
+ mIntentFilter.addAction(ACTION_CLOSE_PIP);
+ mIntentFilter.addAction(ACTION_SHOW_PIP_MENU);
+ mIntentFilter.addAction(ACTION_MOVE_PIP);
+ mIntentFilter.addAction(ACTION_TOGGLE_EXPANDED_PIP);
+ mIntentFilter.addAction(ACTION_TO_FULLSCREEN);
+ }
+
+ boolean mRegistered = false;
+
+ void register() {
+ if (mRegistered) return;
+
+ mContext.registerReceiverForAllUsers(this, mIntentFilter, SYSTEMUI_PERMISSION,
+ mMainHandler);
+ mRegistered = true;
+ }
+
+ void unregister() {
+ if (!mRegistered) return;
+
+ mContext.unregisterReceiver(this);
+ mRegistered = false;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: on(Broadcast)Receive(), action=%s", TAG, action);
+
+ if (ACTION_SHOW_PIP_MENU.equals(action)) {
+ showPictureInPictureMenu(/* moveMenu= */ false);
+ } else {
+ executeAction(getCorrespondingActionType(action));
+ }
+ }
+
+ @TvPipAction.ActionType
+ private int getCorrespondingActionType(String broadcast) {
+ if (ACTION_CLOSE_PIP.equals(broadcast)) {
+ return TvPipAction.ACTION_CLOSE;
+ } else if (ACTION_MOVE_PIP.equals(broadcast)) {
+ return TvPipAction.ACTION_MOVE;
+ } else if (ACTION_TOGGLE_EXPANDED_PIP.equals(broadcast)) {
+ return TvPipAction.ACTION_EXPAND_COLLAPSE;
+ } else if (ACTION_TO_FULLSCREEN.equals(broadcast)) {
+ return TvPipAction.ACTION_FULLSCREEN;
+ }
+
+ // Default: handle it like an action we don't know the content of.
+ return TvPipAction.ACTION_CUSTOM;
+ }
+ }
+
private class TvPipImpl implements Pip {
// Not used
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java
new file mode 100644
index 000000000000..449a2bf09881
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip.tv;
+
+import static android.app.Notification.Action.SEMANTIC_ACTION_DELETE;
+import static android.app.Notification.Action.SEMANTIC_ACTION_NONE;
+
+import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.RemoteAction;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.TvWindowMenuActionButton;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A TvPipAction for actions that the app provides via {@link
+ * android.app.PictureInPictureParams.Builder#setCloseAction(RemoteAction)} or {@link
+ * android.app.PictureInPictureParams.Builder#setActions(List)}.
+ */
+public class TvPipCustomAction extends TvPipAction {
+ private static final String TAG = TvPipCustomAction.class.getSimpleName();
+
+ private final RemoteAction mRemoteAction;
+
+ TvPipCustomAction(@ActionType int actionType, @NonNull RemoteAction remoteAction,
+ SystemActionsHandler systemActionsHandler) {
+ super(actionType, systemActionsHandler);
+ Objects.requireNonNull(remoteAction);
+ mRemoteAction = remoteAction;
+ }
+
+ void populateButton(@NonNull TvWindowMenuActionButton button, Handler mainHandler) {
+ if (button == null || mainHandler == null) return;
+ if (mRemoteAction.getContentDescription().length() > 0) {
+ button.setTextAndDescription(mRemoteAction.getContentDescription());
+ } else {
+ button.setTextAndDescription(mRemoteAction.getTitle());
+ }
+ button.setImageIconAsync(mRemoteAction.getIcon(), mainHandler);
+ button.setEnabled(isCloseAction() || mRemoteAction.isEnabled());
+ }
+
+ PendingIntent getPendingIntent() {
+ return mRemoteAction.getActionIntent();
+ }
+
+ void executeAction() {
+ super.executeAction();
+ try {
+ mRemoteAction.getActionIntent().send();
+ } catch (PendingIntent.CanceledException e) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Failed to send action, %s", TAG, e);
+ }
+ }
+
+ @Override
+ Notification.Action toNotificationAction(Context context) {
+ Notification.Action.Builder builder = new Notification.Action.Builder(
+ mRemoteAction.getIcon(),
+ mRemoteAction.getTitle(),
+ mRemoteAction.getActionIntent());
+ Bundle extras = new Bundle();
+ extras.putCharSequence(Notification.EXTRA_PICTURE_CONTENT_DESCRIPTION,
+ mRemoteAction.getContentDescription());
+ builder.addExtras(extras);
+
+ builder.setSemanticAction(isCloseAction()
+ ? SEMANTIC_ACTION_DELETE : SEMANTIC_ACTION_NONE);
+ builder.setContextual(true);
+ return builder.build();
+ }
+
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index ab7edbfaa4ca..00e4f47e3c6b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -41,13 +41,10 @@ import androidx.annotation.Nullable;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.SystemWindows;
-import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipMenuController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import java.util.ArrayList;
import java.util.List;
-import java.util.Objects;
/**
* Manages the visibility of the PiP Menu as user interacts with PiP.
@@ -60,22 +57,20 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
private final SystemWindows mSystemWindows;
private final TvPipBoundsState mTvPipBoundsState;
private final Handler mMainHandler;
+ private TvPipActionsProvider mTvPipActionsProvider;
private Delegate mDelegate;
private SurfaceControl mLeash;
private TvPipMenuView mPipMenuView;
private View mPipBackgroundView;
+ private boolean mMenuIsOpen;
// User can actively move the PiP via the DPAD.
private boolean mInMoveMode;
// Used when only showing the move menu since we want to close the menu completely when
// exiting the move menu instead of showing the regular button menu.
private boolean mCloseAfterExitMoveMenu;
- private final List<RemoteAction> mMediaActions = new ArrayList<>();
- private final List<RemoteAction> mAppActions = new ArrayList<>();
- private RemoteAction mCloseAction;
-
private SyncRtSurfaceTransactionApplier mApplier;
private SyncRtSurfaceTransactionApplier mBackgroundApplier;
RectF mTmpSourceRectF = new RectF();
@@ -83,8 +78,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
Matrix mMoveTransform = new Matrix();
public TvPipMenuController(Context context, TvPipBoundsState tvPipBoundsState,
- SystemWindows systemWindows, PipMediaController pipMediaController,
- Handler mainHandler) {
+ SystemWindows systemWindows, Handler mainHandler) {
mContext = context;
mTvPipBoundsState = tvPipBoundsState;
mSystemWindows = systemWindows;
@@ -101,9 +95,6 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
context.registerReceiverForAllUsers(closeSystemDialogsBroadcastReceiver,
new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), null /* permission */,
mainHandler, Context.RECEIVER_EXPORTED);
-
- pipMediaController.addActionListener(this::onMediaActionsChanged);
-
}
void setDelegate(Delegate delegate) {
@@ -120,6 +111,10 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
mDelegate = delegate;
}
+ void setTvPipActionsProvider(TvPipActionsProvider tvPipActionsProvider) {
+ mTvPipActionsProvider = tvPipActionsProvider;
+ }
+
@Override
public void attach(SurfaceControl leash) {
if (mDelegate == null) {
@@ -146,15 +141,19 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
int pipMenuBorderWidth = mContext.getResources()
.getDimensionPixelSize(R.dimen.pip_menu_border_width);
mTvPipBoundsState.setPipMenuPermanentDecorInsets(Insets.of(-pipMenuBorderWidth,
- -pipMenuBorderWidth, -pipMenuBorderWidth, -pipMenuBorderWidth));
+ -pipMenuBorderWidth, -pipMenuBorderWidth, -pipMenuBorderWidth));
mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.of(0, 0, 0, -pipEduTextHeight));
}
private void attachPipMenuView() {
- mPipMenuView = new TvPipMenuView(mContext, mMainHandler, this);
+ if (mTvPipActionsProvider == null) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Actions provider is not set", TAG);
+ return;
+ }
+ mPipMenuView = new TvPipMenuView(mContext, mMainHandler, this, mTvPipActionsProvider);
setUpViewSurfaceZOrder(mPipMenuView, 1);
addPipMenuViewToSystemWindows(mPipMenuView, MENU_WINDOW_TITLE);
- maybeUpdateMenuViewActions();
}
private void attachPipBackgroundView() {
@@ -189,17 +188,22 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
// and the menu view has been fully remeasured and relaid out, we add a small delay here by
// posting on the handler.
mMainHandler.post(() -> {
- mPipMenuView.onPipTransitionFinished(
- enterTransition, mTvPipBoundsState.isTvPipExpanded());
+ if (mPipMenuView != null) {
+ mPipMenuView.onPipTransitionFinished(enterTransition);
+ }
});
}
- void showMovementMenuOnly() {
+ void showMovementMenu() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: showMovementMenuOnly()", TAG);
setInMoveMode(true);
- mCloseAfterExitMoveMenu = true;
- showMenuInternal();
+ if (mMenuIsOpen) {
+ mPipMenuView.showMoveMenu(mDelegate.getPipGravity());
+ } else {
+ mCloseAfterExitMoveMenu = true;
+ showMenuInternal();
+ }
}
@Override
@@ -214,14 +218,13 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
if (mPipMenuView == null) {
return;
}
- maybeUpdateMenuViewActions();
- updateExpansionState();
+ mMenuIsOpen = true;
grantPipMenuFocus(true);
if (mInMoveMode) {
mPipMenuView.showMoveMenu(mDelegate.getPipGravity());
} else {
- mPipMenuView.showButtonsMenu();
+ mPipMenuView.showButtonsMenu(/* exitingMoveMode= */ false);
}
mPipMenuView.updateBounds(mTvPipBoundsState.getBounds());
}
@@ -236,11 +239,6 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
mPipMenuView.showMovementHints(gravity);
}
- void updateExpansionState() {
- mPipMenuView.setExpandedModeEnabled(mTvPipBoundsState.isTvExpandedPipSupported()
- && mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0);
- }
-
private Rect calculateMenuSurfaceBounds(Rect pipBounds) {
return mPipMenuView.getPipMenuContainerBounds(pipBounds);
}
@@ -252,6 +250,8 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
if (mPipMenuView == null) {
return;
}
+
+ mMenuIsOpen = false;
mPipMenuView.hideAllUserControls();
grantPipMenuFocus(false);
mDelegate.onMenuClosed();
@@ -272,29 +272,19 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
}
@Override
- public void onEnterMoveMode() {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: onEnterMoveMode - %b, close when exiting move menu: %b", TAG, mInMoveMode,
- mCloseAfterExitMoveMenu);
- setInMoveMode(true);
- mPipMenuView.showMoveMenu(mDelegate.getPipGravity());
- }
-
- @Override
public boolean onExitMoveMode() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: onExitMoveMode - %b, close when exiting move menu: %b", TAG, mInMoveMode,
- mCloseAfterExitMoveMenu);
+ "%s: onExitMoveMode - %b, close when exiting move menu: %b",
+ TAG, mInMoveMode, mCloseAfterExitMoveMenu);
- if (mCloseAfterExitMoveMenu) {
- setInMoveMode(false);
- mCloseAfterExitMoveMenu = false;
- closeMenu();
- return true;
- }
if (mInMoveMode) {
setInMoveMode(false);
- mPipMenuView.showButtonsMenu();
+ if (mCloseAfterExitMoveMenu) {
+ mCloseAfterExitMoveMenu = false;
+ closeMenu();
+ } else {
+ mPipMenuView.showButtonsMenu(/* exitingMoveMode= */ true);
+ }
return true;
}
return false;
@@ -319,51 +309,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
@Override
public void setAppActions(List<RemoteAction> actions, RemoteAction closeAction) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: setAppActions()", TAG);
- updateAdditionalActionsList(mAppActions, actions, closeAction);
- }
-
- private void onMediaActionsChanged(List<RemoteAction> actions) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: onMediaActionsChanged()", TAG);
-
- // Hide disabled actions.
- List<RemoteAction> enabledActions = new ArrayList<>();
- for (RemoteAction remoteAction : actions) {
- if (remoteAction.isEnabled()) {
- enabledActions.add(remoteAction);
- }
- }
- updateAdditionalActionsList(mMediaActions, enabledActions, mCloseAction);
- }
-
- private void updateAdditionalActionsList(List<RemoteAction> destination,
- @Nullable List<RemoteAction> source, RemoteAction closeAction) {
- final int number = source != null ? source.size() : 0;
- if (number == 0 && destination.isEmpty() && Objects.equals(closeAction, mCloseAction)) {
- // Nothing changed.
- return;
- }
-
- mCloseAction = closeAction;
-
- destination.clear();
- if (number > 0) {
- destination.addAll(source);
- }
- maybeUpdateMenuViewActions();
- }
-
- private void maybeUpdateMenuViewActions() {
- if (mPipMenuView == null) {
- return;
- }
- if (!mAppActions.isEmpty()) {
- mPipMenuView.setAdditionalActions(mAppActions, mCloseAction, mMainHandler);
- } else {
- mPipMenuView.setAdditionalActions(mMediaActions, mCloseAction, mMainHandler);
- }
+ // NOOP - handled via the TvPipActionsProvider
}
@Override
@@ -544,42 +490,21 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
}
@Override
- public void onCloseButtonClick() {
- mDelegate.closePip();
- }
-
- @Override
- public void onFullscreenButtonClick() {
- mDelegate.movePipToFullscreen();
- }
-
- @Override
- public void onToggleExpandedMode() {
- mDelegate.togglePipExpansion();
- }
-
- @Override
public void onCloseEduText() {
mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.NONE);
mDelegate.closeEduText();
}
interface Delegate {
- void movePipToFullscreen();
-
void movePip(int keycode);
void onInMoveModeChanged();
int getPipGravity();
- void togglePipExpansion();
-
void onMenuClosed();
void closeEduText();
-
- void closePip();
}
private void grantPipMenuFocus(boolean grantFocus) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index 57e95c416b3c..56c602a1d4f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -25,119 +25,102 @@ import static android.view.KeyEvent.KEYCODE_DPAD_RIGHT;
import static android.view.KeyEvent.KEYCODE_DPAD_UP;
import static android.view.KeyEvent.KEYCODE_ENTER;
-import android.app.PendingIntent;
-import android.app.RemoteAction;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_MOVE;
+
import android.content.Context;
import android.graphics.Rect;
import android.os.Handler;
import android.view.Gravity;
import android.view.KeyEvent;
-import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewRootImpl;
import android.view.accessibility.AccessibilityManager;
import android.widget.FrameLayout;
-import android.widget.HorizontalScrollView;
import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.ScrollView;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.widget.LinearLayoutManager;
+import com.android.internal.widget.RecyclerView;
import com.android.wm.shell.R;
import com.android.wm.shell.common.TvWindowMenuActionButton;
import com.android.wm.shell.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import java.util.ArrayList;
import java.util.List;
/**
- * A View that represents Pip Menu on TV. It's responsible for displaying 3 ever-present Pip Menu
- * actions: Fullscreen, Move and Close, but could also display "additional" actions, that may be set
- * via a {@link #setAdditionalActions(List, RemoteAction, Handler)} call.
+ * A View that represents Pip Menu on TV. It's responsible for displaying the Pip menu actions from
+ * the TvPipActionsProvider as well as the buttons for manually moving the PiP.
*/
-public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
+public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.Listener {
private static final String TAG = "TvPipMenuView";
- private static final int FIRST_CUSTOM_ACTION_POSITION = 3;
+ private final TvPipMenuView.Listener mListener;
- private final Listener mListener;
+ private final TvPipActionsProvider mTvPipActionsProvider;
+
+ private final RecyclerView mActionButtonsRecyclerView;
+ private final LinearLayoutManager mButtonLayoutManager;
+ private final RecyclerViewAdapter mRecyclerViewAdapter;
- private final LinearLayout mActionButtonsContainer;
- private final View mMenuFrameView;
- private final List<TvWindowMenuActionButton> mAdditionalButtons = new ArrayList<>();
private final View mPipFrameView;
+ private final View mMenuFrameView;
private final View mPipView;
+
+ private final View mPipBackground;
+ private final View mDimLayer;
+
private final TvPipMenuEduTextDrawer mEduTextDrawer;
+
private final int mPipMenuOuterSpace;
private final int mPipMenuBorderWidth;
+ private final int mPipMenuFadeAnimationDuration;
+ private final int mResizeAnimationDuration;
+
private final ImageView mArrowUp;
private final ImageView mArrowRight;
private final ImageView mArrowDown;
private final ImageView mArrowLeft;
private final TvWindowMenuActionButton mA11yDoneButton;
- private final View mPipBackground;
- private final View mDimLayer;
-
- private final ScrollView mScrollView;
- private final HorizontalScrollView mHorizontalScrollView;
- private View mFocusedButton;
-
private Rect mCurrentPipBounds;
private boolean mMoveMenuIsVisible;
private boolean mButtonMenuIsVisible;
-
- private final TvWindowMenuActionButton mExpandButton;
- private final TvWindowMenuActionButton mCloseButton;
-
private boolean mSwitchingOrientation;
- private final int mPipMenuFadeAnimationDuration;
- private final int mResizeAnimationDuration;
-
private final AccessibilityManager mA11yManager;
private final Handler mMainHandler;
public TvPipMenuView(@NonNull Context context, @NonNull Handler mainHandler,
- @NonNull Listener listener) {
+ @NonNull Listener listener, TvPipActionsProvider tvPipActionsProvider) {
super(context, null, 0, 0);
-
inflate(context, R.layout.tv_pip_menu, this);
mMainHandler = mainHandler;
mListener = listener;
-
mA11yManager = context.getSystemService(AccessibilityManager.class);
- mActionButtonsContainer = findViewById(R.id.tv_pip_menu_action_buttons);
- mActionButtonsContainer.findViewById(R.id.tv_pip_menu_fullscreen_button)
- .setOnClickListener(this);
-
- mCloseButton = mActionButtonsContainer.findViewById(R.id.tv_pip_menu_close_button);
- mCloseButton.setOnClickListener(this);
- mCloseButton.setIsCustomCloseAction(true);
+ mActionButtonsRecyclerView = findViewById(R.id.tv_pip_menu_action_buttons);
+ mButtonLayoutManager = new LinearLayoutManager(mContext);
+ mActionButtonsRecyclerView.setLayoutManager(mButtonLayoutManager);
+ mActionButtonsRecyclerView.setPreserveFocusAfterLayout(true);
- mActionButtonsContainer.findViewById(R.id.tv_pip_menu_move_button)
- .setOnClickListener(this);
- mExpandButton = findViewById(R.id.tv_pip_menu_expand_button);
- mExpandButton.setOnClickListener(this);
+ mTvPipActionsProvider = tvPipActionsProvider;
+ mRecyclerViewAdapter = new RecyclerViewAdapter(tvPipActionsProvider.getActionsList());
+ mActionButtonsRecyclerView.setAdapter(mRecyclerViewAdapter);
- mPipBackground = findViewById(R.id.tv_pip_menu_background);
- mDimLayer = findViewById(R.id.tv_pip_menu_dim_layer);
-
- mScrollView = findViewById(R.id.tv_pip_menu_scroll);
- mHorizontalScrollView = findViewById(R.id.tv_pip_menu_horizontal_scroll);
+ tvPipActionsProvider.addListener(this);
mMenuFrameView = findViewById(R.id.tv_pip_menu_frame);
mPipFrameView = findViewById(R.id.tv_pip_border);
mPipView = findViewById(R.id.tv_pip);
+ mPipBackground = findViewById(R.id.tv_pip_menu_background);
+ mDimLayer = findViewById(R.id.tv_pip_menu_dim_layer);
+
mArrowUp = findViewById(R.id.tv_pip_menu_arrow_up);
mArrowRight = findViewById(R.id.tv_pip_menu_arrow_right);
mArrowDown = findViewById(R.id.tv_pip_menu_arrow_down);
@@ -160,8 +143,12 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
}
void onPipTransitionToTargetBoundsStarted(Rect targetBounds) {
+ if (targetBounds == null) {
+ return;
+ }
+
// Fade out content by fading in view on top.
- if (mCurrentPipBounds != null && targetBounds != null) {
+ if (mCurrentPipBounds != null) {
boolean ratioChanged = PipUtils.aspectRatioChanged(
mCurrentPipBounds.width() / (float) mCurrentPipBounds.height(),
targetBounds.width() / (float) targetBounds.height());
@@ -177,7 +164,7 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
// Update buttons.
final boolean vertical = targetBounds.height() > targetBounds.width();
final boolean orientationChanged =
- vertical != (mActionButtonsContainer.getOrientation() == LinearLayout.VERTICAL);
+ vertical != (mButtonLayoutManager.getOrientation() == LinearLayoutManager.VERTICAL);
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: onPipTransitionToTargetBoundsStarted(), orientation changed %b",
TAG, orientationChanged);
@@ -187,27 +174,27 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
if (mButtonMenuIsVisible) {
mSwitchingOrientation = true;
- mActionButtonsContainer.animate()
+ mActionButtonsRecyclerView.animate()
.alpha(0)
.setInterpolator(TvPipInterpolators.EXIT)
.setDuration(mResizeAnimationDuration / 2)
.withEndAction(() -> {
- changeButtonScrollOrientation(targetBounds);
- updateButtonGravity(targetBounds);
+ mButtonLayoutManager.setOrientation(vertical
+ ? LinearLayoutManager.VERTICAL : LinearLayoutManager.HORIZONTAL);
// Only make buttons visible again in onPipTransitionFinished to keep in
// sync with PiP content alpha animation.
});
} else {
- changeButtonScrollOrientation(targetBounds);
- updateButtonGravity(targetBounds);
+ mButtonLayoutManager.setOrientation(vertical
+ ? LinearLayoutManager.VERTICAL : LinearLayoutManager.HORIZONTAL);
}
}
- void onPipTransitionFinished(boolean enterTransition, boolean isTvPipExpanded) {
+ void onPipTransitionFinished(boolean enterTransition) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: onPipTransitionFinished()", TAG);
- // Fade in content by fading out view on top.
+ // Fade in content by fading out view on top (faded out at every aspect ratio change).
mPipBackground.animate()
.alpha(0f)
.setDuration(mResizeAnimationDuration / 2)
@@ -218,16 +205,11 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
mEduTextDrawer.init();
}
- setIsExpanded(isTvPipExpanded);
-
- // Update buttons.
if (mSwitchingOrientation) {
- mActionButtonsContainer.animate()
+ mActionButtonsRecyclerView.animate()
.alpha(1)
.setInterpolator(TvPipInterpolators.ENTER)
.setDuration(mResizeAnimationDuration / 2);
- } else {
- refocusPreviousButton();
}
mSwitchingOrientation = false;
}
@@ -240,107 +222,9 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
"%s: updateLayout, width: %s, height: %s", TAG, updatedBounds.width(),
updatedBounds.height());
mCurrentPipBounds = updatedBounds;
- if (!mSwitchingOrientation) {
- updateButtonGravity(mCurrentPipBounds);
- }
-
updatePipFrameBounds();
}
- private void changeButtonScrollOrientation(Rect bounds) {
- final boolean vertical = bounds.height() > bounds.width();
-
- final ViewGroup oldScrollView = vertical ? mHorizontalScrollView : mScrollView;
- final ViewGroup newScrollView = vertical ? mScrollView : mHorizontalScrollView;
-
- if (oldScrollView.getChildCount() == 1) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: orientation changed", TAG);
- oldScrollView.removeView(mActionButtonsContainer);
- oldScrollView.setVisibility(GONE);
- mActionButtonsContainer.setOrientation(vertical ? LinearLayout.VERTICAL
- : LinearLayout.HORIZONTAL);
- newScrollView.addView(mActionButtonsContainer);
- newScrollView.setVisibility(VISIBLE);
- if (mFocusedButton != null) {
- mFocusedButton.requestFocus();
- }
- }
- }
-
- /**
- * Change button gravity based on new dimensions
- */
- private void updateButtonGravity(Rect bounds) {
- final boolean vertical = bounds.height() > bounds.width();
- // Use Math.max since the possible orientation change might not have been applied yet.
- final int buttonsSize = Math.max(mActionButtonsContainer.getHeight(),
- mActionButtonsContainer.getWidth());
-
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: buttons container width: %s, height: %s", TAG,
- mActionButtonsContainer.getWidth(), mActionButtonsContainer.getHeight());
-
- final boolean buttonsFit =
- vertical ? buttonsSize < bounds.height()
- : buttonsSize < bounds.width();
- final int buttonGravity = buttonsFit ? Gravity.CENTER
- : (vertical ? Gravity.CENTER_HORIZONTAL : Gravity.CENTER_VERTICAL);
-
- final LayoutParams params = (LayoutParams) mActionButtonsContainer.getLayoutParams();
- params.gravity = buttonGravity;
- mActionButtonsContainer.setLayoutParams(params);
-
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: vertical: %b, buttonsFit: %b, gravity: %s", TAG, vertical, buttonsFit,
- Gravity.toString(buttonGravity));
- }
-
- private void refocusPreviousButton() {
- if (mMoveMenuIsVisible || mCurrentPipBounds == null || mFocusedButton == null) {
- return;
- }
- final boolean vertical = mCurrentPipBounds.height() > mCurrentPipBounds.width();
-
- if (!mFocusedButton.hasFocus()) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: request focus from: %s", TAG, mFocusedButton);
- mFocusedButton.requestFocus();
- } else {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: already focused: %s", TAG, mFocusedButton);
- }
-
- // Do we need to scroll?
- final Rect buttonBounds = new Rect();
- final Rect scrollBounds = new Rect();
- if (vertical) {
- mScrollView.getDrawingRect(scrollBounds);
- } else {
- mHorizontalScrollView.getDrawingRect(scrollBounds);
- }
- mFocusedButton.getHitRect(buttonBounds);
-
- if (scrollBounds.contains(buttonBounds)) {
- // Button is already completely visible, don't scroll
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: not scrolling", TAG);
- return;
- }
-
- // Scrolling so the button is visible to the user.
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: scrolling to focused button", TAG);
-
- if (vertical) {
- mScrollView.smoothScrollTo((int) mFocusedButton.getX(),
- (int) mFocusedButton.getY());
- } else {
- mHorizontalScrollView.smoothScrollTo((int) mFocusedButton.getX(),
- (int) mFocusedButton.getY());
- }
- }
-
Rect getPipMenuContainerBounds(Rect pipBounds) {
final Rect menuUiBounds = new Rect(pipBounds);
menuUiBounds.inset(-mPipMenuOuterSpace, -mPipMenuOuterSpace);
@@ -370,20 +254,14 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
mPipView.setLayoutParams(pipViewParams);
}
-
- }
-
- void setExpandedModeEnabled(boolean enabled) {
- mExpandButton.setVisibility(enabled ? VISIBLE : GONE);
- }
-
- void setIsExpanded(boolean expanded) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: setIsExpanded, expanded: %b", TAG, expanded);
- mExpandButton.setImageResource(
- expanded ? R.drawable.pip_ic_collapse : R.drawable.pip_ic_expand);
- mExpandButton.setTextAndDescription(
- expanded ? R.string.pip_collapse : R.string.pip_expand);
+ // Keep focused button within the visible area while the PiP is changing size. Otherwise,
+ // the button would lose focus which would cause a need for scrolling and re-focusing after
+ // the animation finishes, which does not look good.
+ View focusedChild = mActionButtonsRecyclerView.getFocusedChild();
+ if (focusedChild != null) {
+ mActionButtonsRecyclerView.scrollToPosition(
+ mActionButtonsRecyclerView.getChildLayoutPosition(focusedChild));
+ }
}
/**
@@ -391,48 +269,63 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
*/
void showMoveMenu(int gravity) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMoveMenu()", TAG);
- showButtonsMenu(false);
showMovementHints(gravity);
+ setMenuButtonsVisible(false);
setFrameHighlighted(true);
- mHorizontalScrollView.setFocusable(false);
- mScrollView.setFocusable(false);
+ animateAlphaTo(mA11yManager.isEnabled() ? 1f : 0f, mDimLayer);
mEduTextDrawer.closeIfNeeded();
}
- void showButtonsMenu() {
+
+ void showButtonsMenu(boolean exitingMoveMode) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: showButtonsMenu()", TAG);
- showButtonsMenu(true);
+ "%s: showButtonsMenu(), exitingMoveMode %b", TAG, exitingMoveMode);
+ setMenuButtonsVisible(true);
hideMovementHints();
setFrameHighlighted(true);
+ animateAlphaTo(1f, mDimLayer);
+ mEduTextDrawer.closeIfNeeded();
- mHorizontalScrollView.setFocusable(true);
- mScrollView.setFocusable(true);
-
- // Always focus on the first button when opening the menu, except directly after moving.
- if (mFocusedButton == null) {
- // Focus on first button (there is a Space at position 0)
- mFocusedButton = mActionButtonsContainer.getChildAt(1);
- // Reset scroll position.
- mScrollView.scrollTo(0, 0);
- mHorizontalScrollView.scrollTo(
- isLayoutRtl() ? mActionButtonsContainer.getWidth() : 0, 0);
+ if (exitingMoveMode) {
+ scrollAndRefocusButton(mTvPipActionsProvider.getFirstIndexOfAction(ACTION_MOVE),
+ /* alwaysScroll= */ false);
+ } else {
+ scrollAndRefocusButton(0, /* alwaysScroll= */ true);
+ }
+ }
+
+ private void scrollAndRefocusButton(int position, boolean alwaysScroll) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: scrollAndRefocusButton, target: %d", TAG, position);
+
+ if (alwaysScroll || !refocusButton(position)) {
+ mButtonLayoutManager.scrollToPositionWithOffset(position, 0);
+ mActionButtonsRecyclerView.post(() -> refocusButton(position));
}
- refocusPreviousButton();
}
/**
- * Hides all menu views, including the menu frame.
+ * @return true if focus was requested, false if focus request could not be carried out due to
+ * the view for the position not being available (scrolling beforehand will be necessary).
*/
+ private boolean refocusButton(int position) {
+ View itemToFocus = mButtonLayoutManager.findViewByPosition(position);
+ if (itemToFocus != null) {
+ itemToFocus.requestFocus();
+ itemToFocus.requestAccessibilityFocus();
+ }
+ return itemToFocus != null;
+ }
+
void hideAllUserControls() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: hideAllUserControls()", TAG);
- mFocusedButton = null;
- showButtonsMenu(false);
+ setMenuButtonsVisible(false);
hideMovementHints();
setFrameHighlighted(false);
+ animateAlphaTo(0f, mDimLayer);
}
@Override
@@ -463,134 +356,19 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
});
}
- /**
- * Button order:
- * - Fullscreen
- * - Close
- * - Custom actions (app or media actions)
- * - System actions
- */
- void setAdditionalActions(List<RemoteAction> actions, RemoteAction closeAction,
- Handler mainHandler) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: setAdditionalActions()", TAG);
-
- // Replace system close action with custom close action if available
- if (closeAction != null) {
- setActionForButton(closeAction, mCloseButton, mainHandler);
- } else {
- mCloseButton.setTextAndDescription(R.string.pip_close);
- mCloseButton.setImageResource(R.drawable.pip_ic_close_white);
- }
- mCloseButton.setIsCustomCloseAction(closeAction != null);
- // Make sure the close action is always enabled
- mCloseButton.setEnabled(true);
-
- // Make sure we exactly as many additional buttons as we have actions to display.
- final int actionsNumber = actions.size();
- int buttonsNumber = mAdditionalButtons.size();
- if (actionsNumber > buttonsNumber) {
- // Add buttons until we have enough to display all the actions.
- while (actionsNumber > buttonsNumber) {
- TvWindowMenuActionButton button = new TvWindowMenuActionButton(mContext);
- button.setOnClickListener(this);
-
- mActionButtonsContainer.addView(button,
- FIRST_CUSTOM_ACTION_POSITION + buttonsNumber);
- mAdditionalButtons.add(button);
-
- buttonsNumber++;
- }
- } else if (actionsNumber < buttonsNumber) {
- // Hide buttons until we as many as the actions.
- while (actionsNumber < buttonsNumber) {
- final View button = mAdditionalButtons.get(buttonsNumber - 1);
- button.setVisibility(View.GONE);
- button.setTag(null);
-
- buttonsNumber--;
- }
- }
-
- // "Assign" actions to the buttons.
- for (int index = 0; index < actionsNumber; index++) {
- final RemoteAction action = actions.get(index);
- final TvWindowMenuActionButton button = mAdditionalButtons.get(index);
-
- // Remove action if it matches the custom close action.
- if (PipUtils.remoteActionsMatch(action, closeAction)) {
- button.setVisibility(GONE);
- continue;
- }
- setActionForButton(action, button, mainHandler);
- }
-
- if (mCurrentPipBounds != null) {
- updateButtonGravity(mCurrentPipBounds);
- refocusPreviousButton();
- }
- }
-
- private void setActionForButton(RemoteAction action, TvWindowMenuActionButton button,
- Handler mainHandler) {
- button.setVisibility(View.VISIBLE); // Ensure the button is visible.
- if (action.getContentDescription().length() > 0) {
- button.setTextAndDescription(action.getContentDescription());
- } else {
- button.setTextAndDescription(action.getTitle());
- }
- button.setEnabled(action.isEnabled());
- button.setTag(action);
- action.getIcon().loadDrawableAsync(mContext, button::setImageDrawable, mainHandler);
- }
-
- @Nullable
- SurfaceControl getWindowSurfaceControl() {
- final ViewRootImpl root = getViewRootImpl();
- if (root == null) {
- return null;
- }
- final SurfaceControl out = root.getSurfaceControl();
- if (out != null && out.isValid()) {
- return out;
- }
- return null;
- }
-
@Override
- public void onClick(View v) {
- final int id = v.getId();
- if (id == R.id.tv_pip_menu_fullscreen_button) {
- mListener.onFullscreenButtonClick();
- } else if (id == R.id.tv_pip_menu_move_button) {
- mListener.onEnterMoveMode();
- } else if (id == R.id.tv_pip_menu_close_button) {
- mListener.onCloseButtonClick();
- } else if (id == R.id.tv_pip_menu_expand_button) {
- mListener.onToggleExpandedMode();
- } else {
- // This should be an "additional action"
- final RemoteAction action = (RemoteAction) v.getTag();
- if (action != null) {
- try {
- action.getActionIntent().send();
- } catch (PendingIntent.CanceledException e) {
- ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: Failed to send action, %s", TAG, e);
- }
- } else {
- ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: RemoteAction is null", TAG);
- }
+ public void onActionsChanged(int added, int updated, int startIndex) {
+ mRecyclerViewAdapter.notifyItemRangeChanged(startIndex, updated);
+ if (added > 0) {
+ mRecyclerViewAdapter.notifyItemRangeInserted(startIndex + updated, added);
+ } else if (added < 0) {
+ mRecyclerViewAdapter.notifyItemRangeRemoved(startIndex + updated, -added);
}
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getAction() == ACTION_UP) {
- if (!mMoveMenuIsVisible) {
- mFocusedButton = mActionButtonsContainer.getFocusedChild();
- }
if (event.getKeyCode() == KEYCODE_BACK) {
mListener.onBackPress();
@@ -624,10 +402,6 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
public void showMovementHints(int gravity) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: showMovementHints(), position: %s", TAG, Gravity.toString(gravity));
-
- if (mMoveMenuIsVisible) {
- return;
- }
mMoveMenuIsVisible = true;
animateAlphaTo(checkGravity(gravity, Gravity.BOTTOM) ? 1f : 0f, mArrowUp);
@@ -643,9 +417,12 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
animateAlphaTo(a11yEnabled ? 1f : 0f, mA11yDoneButton);
if (a11yEnabled) {
+ mA11yDoneButton.setVisibility(VISIBLE);
mA11yDoneButton.setOnClickListener(v -> {
mListener.onExitMoveMode();
});
+ mA11yDoneButton.requestFocus();
+ mA11yDoneButton.requestAccessibilityFocus();
}
}
@@ -684,33 +461,67 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
/**
* Show or hide the pip buttons menu.
*/
- public void showButtonsMenu(boolean show) {
+ private void setMenuButtonsVisible(boolean visible) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: showUserActions: %b", TAG, show);
- if (mButtonMenuIsVisible == show) {
- return;
- }
- mButtonMenuIsVisible = show;
-
- if (show) {
- mActionButtonsContainer.setVisibility(VISIBLE);
- refocusPreviousButton();
- }
- animateAlphaTo(show ? 1 : 0, mActionButtonsContainer);
- animateAlphaTo(show ? 1 : 0, mDimLayer);
- mEduTextDrawer.closeIfNeeded();
+ "%s: showUserActions: %b", TAG, visible);
+ mButtonMenuIsVisible = visible;
+ animateAlphaTo(visible ? 1 : 0, mActionButtonsRecyclerView);
}
private void setFrameHighlighted(boolean highlighted) {
mMenuFrameView.setActivated(highlighted);
}
+ private class RecyclerViewAdapter extends
+ RecyclerView.Adapter<RecyclerViewAdapter.ButtonViewHolder> {
+
+ private final List<TvPipAction> mActionList;
+
+ RecyclerViewAdapter(List<TvPipAction> actionList) {
+ this.mActionList = actionList;
+ }
+
+ @NonNull
+ @Override
+ public ButtonViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ return new ButtonViewHolder(new TvWindowMenuActionButton(mContext));
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ButtonViewHolder holder, int position) {
+ TvPipAction action = mActionList.get(position);
+ action.populateButton(holder.mButton, mMainHandler);
+ }
+
+ @Override
+ public int getItemCount() {
+ return mActionList.size();
+ }
+
+ private class ButtonViewHolder extends RecyclerView.ViewHolder implements OnClickListener {
+ TvWindowMenuActionButton mButton;
+
+ ButtonViewHolder(@NonNull View itemView) {
+ super(itemView);
+ mButton = (TvWindowMenuActionButton) itemView;
+ mButton.setOnClickListener(this);
+ }
+
+ @Override
+ public void onClick(View v) {
+ TvPipAction action = mActionList.get(
+ mActionButtonsRecyclerView.getChildLayoutPosition(v));
+ if (action != null) {
+ action.executeAction();
+ }
+ }
+ }
+ }
+
interface Listener extends TvPipMenuEduTextDrawer.Listener {
void onBackPress();
- void onEnterMoveMode();
-
/**
* Called when a button for exiting move mode was pressed.
*
@@ -723,11 +534,5 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
* @return whether pip movement was handled.
*/
boolean onPipMovement(int keycode);
-
- void onCloseButtonClick();
-
- void onFullscreenButtonClick();
-
- void onToggleExpandedMode();
}
-}
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
index e3308f0763a0..f22ee595e6c9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
@@ -16,18 +16,13 @@
package com.android.wm.shell.pip.tv;
-import static android.app.Notification.Action.SEMANTIC_ACTION_DELETE;
-import static android.app.Notification.Action.SEMANTIC_ACTION_NONE;
-
+import android.annotation.NonNull;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
-import android.app.RemoteAction;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
@@ -35,7 +30,6 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.media.session.MediaSession;
import android.os.Bundle;
-import android.os.Handler;
import android.text.TextUtils;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
@@ -47,7 +41,6 @@ import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import java.util.ArrayList;
import java.util.List;
/**
@@ -55,39 +48,18 @@ import java.util.List;
* <p>Once it's created, it will manage the PiP notification UI by itself except for handling
* configuration changes and user initiated expanded PiP toggling.
*/
-public class TvPipNotificationController {
- private static final String TAG = "TvPipNotification";
+public class TvPipNotificationController implements TvPipActionsProvider.Listener {
+ private static final String TAG = TvPipNotificationController.class.getSimpleName();
// Referenced in com.android.systemui.util.NotificationChannels.
public static final String NOTIFICATION_CHANNEL = "TVPIP";
private static final String NOTIFICATION_TAG = "TvPip";
- private static final String SYSTEMUI_PERMISSION = "com.android.systemui.permission.SELF";
-
- private static final String ACTION_SHOW_PIP_MENU =
- "com.android.wm.shell.pip.tv.notification.action.SHOW_PIP_MENU";
- private static final String ACTION_CLOSE_PIP =
- "com.android.wm.shell.pip.tv.notification.action.CLOSE_PIP";
- private static final String ACTION_MOVE_PIP =
- "com.android.wm.shell.pip.tv.notification.action.MOVE_PIP";
- private static final String ACTION_TOGGLE_EXPANDED_PIP =
- "com.android.wm.shell.pip.tv.notification.action.TOGGLE_EXPANDED_PIP";
- private static final String ACTION_FULLSCREEN =
- "com.android.wm.shell.pip.tv.notification.action.FULLSCREEN";
private final Context mContext;
private final PackageManager mPackageManager;
private final NotificationManager mNotificationManager;
private final Notification.Builder mNotificationBuilder;
- private final ActionBroadcastReceiver mActionBroadcastReceiver;
- private final Handler mMainHandler;
- private Delegate mDelegate;
- private final TvPipBoundsState mTvPipBoundsState;
-
- private String mDefaultTitle;
-
- private final List<RemoteAction> mCustomActions = new ArrayList<>();
- private final List<RemoteAction> mMediaActions = new ArrayList<>();
- private RemoteAction mCustomCloseAction;
+ private TvPipActionsProvider mTvPipActionsProvider;
private MediaSession.Token mMediaSessionToken;
@@ -95,19 +67,23 @@ public class TvPipNotificationController {
private String mPackageName;
private boolean mIsNotificationShown;
+ private String mDefaultTitle;
private String mPipTitle;
private String mPipSubtitle;
+ // Saving the actions, so they don't have to be regenerated when e.g. the PiP title changes.
+ @NonNull
+ private Notification.Action[] mPipActions;
+
private Bitmap mActivityIcon;
public TvPipNotificationController(Context context, PipMediaController pipMediaController,
- PipParamsChangedForwarder pipParamsChangedForwarder, TvPipBoundsState tvPipBoundsState,
- Handler mainHandler) {
+ PipParamsChangedForwarder pipParamsChangedForwarder) {
mContext = context;
mPackageManager = context.getPackageManager();
mNotificationManager = context.getSystemService(NotificationManager.class);
- mMainHandler = mainHandler;
- mTvPipBoundsState = tvPipBoundsState;
+
+ mPipActions = new Notification.Action[0];
mNotificationBuilder = new Notification.Builder(context, NOTIFICATION_CHANNEL)
.setLocalOnly(true)
@@ -117,34 +93,15 @@ public class TvPipNotificationController {
.setOnlyAlertOnce(true)
.setSmallIcon(R.drawable.pip_icon)
.setAllowSystemGeneratedContextualActions(false)
- .setContentIntent(createPendingIntent(context, ACTION_FULLSCREEN))
- .setDeleteIntent(getCloseAction().actionIntent)
- .extend(new Notification.TvExtender()
- .setContentIntent(createPendingIntent(context, ACTION_SHOW_PIP_MENU))
- .setDeleteIntent(createPendingIntent(context, ACTION_CLOSE_PIP)));
-
- mActionBroadcastReceiver = new ActionBroadcastReceiver();
+ .setContentIntent(
+ createPendingIntent(context, TvPipController.ACTION_TO_FULLSCREEN));
+ // TvExtender and DeleteIntent set later since they might change.
- pipMediaController.addActionListener(this::onMediaActionsChanged);
pipMediaController.addTokenListener(this::onMediaSessionTokenChanged);
pipParamsChangedForwarder.addListener(
new PipParamsChangedForwarder.PipParamsChangedCallback() {
@Override
- public void onExpandedAspectRatioChanged(float ratio) {
- updateExpansionState();
- }
-
- @Override
- public void onActionsChanged(List<RemoteAction> actions,
- RemoteAction closeAction) {
- mCustomActions.clear();
- mCustomActions.addAll(actions);
- mCustomCloseAction = closeAction;
- updateNotificationContent();
- }
-
- @Override
public void onTitleChanged(String title) {
mPipTitle = title;
updateNotificationContent();
@@ -157,34 +114,33 @@ public class TvPipNotificationController {
}
});
- onConfigurationChanged(context);
+ onConfigurationChanged();
}
- void setDelegate(Delegate delegate) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: setDelegate(), delegate=%s",
- TAG, delegate);
-
- if (mDelegate != null) {
- throw new IllegalStateException(
- "The delegate has already been set and should not change.");
- }
- if (delegate == null) {
- throw new IllegalArgumentException("The delegate must not be null.");
- }
+ /**
+ * Call before showing any notification.
+ */
+ void setTvPipActionsProvider(@NonNull TvPipActionsProvider tvPipActionsProvider) {
+ mTvPipActionsProvider = tvPipActionsProvider;
+ mTvPipActionsProvider.addListener(this);
+ }
- mDelegate = delegate;
+ void onConfigurationChanged() {
+ mDefaultTitle = mContext.getResources().getString(R.string.pip_notification_unknown_title);
+ updateNotificationContent();
}
void show(String packageName) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: show %s", TAG, packageName);
- if (mDelegate == null) {
- throw new IllegalStateException("Delegate is not set.");
+ if (mTvPipActionsProvider == null) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Missing TvPipActionsProvider", TAG);
+ return;
}
mIsNotificationShown = true;
mPackageName = packageName;
mActivityIcon = getActivityIcon();
- mActionBroadcastReceiver.register();
updateNotificationContent();
}
@@ -194,151 +150,42 @@ public class TvPipNotificationController {
mIsNotificationShown = false;
mPackageName = null;
- mActionBroadcastReceiver.unregister();
-
mNotificationManager.cancel(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP);
}
- private Notification.Action getToggleAction(boolean expanded) {
- if (expanded) {
- return createSystemAction(R.drawable.pip_ic_collapse,
- R.string.pip_collapse, ACTION_TOGGLE_EXPANDED_PIP);
- } else {
- return createSystemAction(R.drawable.pip_ic_expand, R.string.pip_expand,
- ACTION_TOGGLE_EXPANDED_PIP);
- }
- }
-
- private Notification.Action createSystemAction(int iconRes, int titleRes, String action) {
- Notification.Action.Builder builder = new Notification.Action.Builder(
- Icon.createWithResource(mContext, iconRes),
- mContext.getString(titleRes),
- createPendingIntent(mContext, action));
- builder.setContextual(true);
- return builder.build();
- }
-
- private void onMediaActionsChanged(List<RemoteAction> actions) {
- mMediaActions.clear();
- mMediaActions.addAll(actions);
- if (mCustomActions.isEmpty()) {
- updateNotificationContent();
- }
- }
-
private void onMediaSessionTokenChanged(MediaSession.Token token) {
mMediaSessionToken = token;
updateNotificationContent();
}
- private Notification.Action remoteToNotificationAction(RemoteAction action) {
- return remoteToNotificationAction(action, SEMANTIC_ACTION_NONE);
- }
-
- private Notification.Action remoteToNotificationAction(RemoteAction action,
- int semanticAction) {
- Notification.Action.Builder builder = new Notification.Action.Builder(action.getIcon(),
- action.getTitle(),
- action.getActionIntent());
- if (action.getContentDescription() != null) {
- Bundle extras = new Bundle();
- extras.putCharSequence(Notification.EXTRA_PICTURE_CONTENT_DESCRIPTION,
- action.getContentDescription());
- builder.addExtras(extras);
- }
- builder.setSemanticAction(semanticAction);
- builder.setContextual(true);
- return builder.build();
- }
-
- private Notification.Action[] getNotificationActions() {
- final List<Notification.Action> actions = new ArrayList<>();
-
- // 1. Fullscreen
- actions.add(getFullscreenAction());
- // 2. Close
- actions.add(getCloseAction());
- // 3. App actions
- final List<RemoteAction> appActions =
- mCustomActions.isEmpty() ? mMediaActions : mCustomActions;
- for (RemoteAction appAction : appActions) {
- if (PipUtils.remoteActionsMatch(mCustomCloseAction, appAction)
- || !appAction.isEnabled()) {
- continue;
- }
- actions.add(remoteToNotificationAction(appAction));
- }
- // 4. Move
- actions.add(getMoveAction());
- // 5. Toggle expansion (if expanded PiP enabled)
- if (mTvPipBoundsState.getDesiredTvExpandedAspectRatio() > 0
- && mTvPipBoundsState.isTvExpandedPipSupported()) {
- actions.add(getToggleAction(mTvPipBoundsState.isTvPipExpanded()));
- }
- return actions.toArray(new Notification.Action[0]);
- }
-
- private Notification.Action getCloseAction() {
- if (mCustomCloseAction == null) {
- return createSystemAction(R.drawable.pip_ic_close_white, R.string.pip_close,
- ACTION_CLOSE_PIP);
- } else {
- return remoteToNotificationAction(mCustomCloseAction, SEMANTIC_ACTION_DELETE);
- }
- }
-
- private Notification.Action getFullscreenAction() {
- return createSystemAction(R.drawable.pip_ic_fullscreen_white,
- R.string.pip_fullscreen, ACTION_FULLSCREEN);
- }
-
- private Notification.Action getMoveAction() {
- return createSystemAction(R.drawable.pip_ic_move_white, R.string.pip_move,
- ACTION_MOVE_PIP);
- }
-
- /**
- * Called by {@link TvPipController} when the configuration is changed.
- */
- void onConfigurationChanged(Context context) {
- mDefaultTitle = context.getResources().getString(R.string.pip_notification_unknown_title);
- updateNotificationContent();
- }
-
- void updateExpansionState() {
- updateNotificationContent();
- }
-
private void updateNotificationContent() {
if (mPackageManager == null || !mIsNotificationShown) {
return;
}
- Notification.Action[] actions = getNotificationActions();
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: update(), title: %s, subtitle: %s, mediaSessionToken: %s, #actions: %s", TAG,
- getNotificationTitle(), mPipSubtitle, mMediaSessionToken, actions.length);
- for (Notification.Action action : actions) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: action: %s", TAG,
- action.toString());
- }
-
+ getNotificationTitle(), mPipSubtitle, mMediaSessionToken, mPipActions.length);
mNotificationBuilder
.setWhen(System.currentTimeMillis())
.setContentTitle(getNotificationTitle())
.setContentText(mPipSubtitle)
.setSubText(getApplicationLabel(mPackageName))
- .setActions(actions);
+ .setActions(mPipActions);
setPipIcon();
Bundle extras = new Bundle();
extras.putParcelable(Notification.EXTRA_MEDIA_SESSION, mMediaSessionToken);
mNotificationBuilder.setExtras(extras);
+ PendingIntent closeIntent = mTvPipActionsProvider.getCloseAction().getPendingIntent();
+ mNotificationBuilder.setDeleteIntent(closeIntent);
// TvExtender not recognized if not set last.
mNotificationBuilder.extend(new Notification.TvExtender()
- .setContentIntent(createPendingIntent(mContext, ACTION_SHOW_PIP_MENU))
- .setDeleteIntent(createPendingIntent(mContext, ACTION_CLOSE_PIP)));
+ .setContentIntent(
+ createPendingIntent(mContext, TvPipController.ACTION_SHOW_PIP_MENU))
+ .setDeleteIntent(closeIntent));
+
mNotificationManager.notify(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP,
mNotificationBuilder.build());
}
@@ -390,68 +237,20 @@ public class TvPipNotificationController {
return ImageUtils.buildScaledBitmap(drawable, width, height, /* allowUpscaling */ true);
}
- private static PendingIntent createPendingIntent(Context context, String action) {
+ static PendingIntent createPendingIntent(Context context, String action) {
return PendingIntent.getBroadcast(context, 0,
new Intent(action).setPackage(context.getPackageName()),
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
}
- private class ActionBroadcastReceiver extends BroadcastReceiver {
- final IntentFilter mIntentFilter;
- {
- mIntentFilter = new IntentFilter();
- mIntentFilter.addAction(ACTION_CLOSE_PIP);
- mIntentFilter.addAction(ACTION_SHOW_PIP_MENU);
- mIntentFilter.addAction(ACTION_MOVE_PIP);
- mIntentFilter.addAction(ACTION_TOGGLE_EXPANDED_PIP);
- mIntentFilter.addAction(ACTION_FULLSCREEN);
- }
- boolean mRegistered = false;
-
- void register() {
- if (mRegistered) return;
-
- mContext.registerReceiverForAllUsers(this, mIntentFilter, SYSTEMUI_PERMISSION,
- mMainHandler);
- mRegistered = true;
- }
-
- void unregister() {
- if (!mRegistered) return;
-
- mContext.unregisterReceiver(this);
- mRegistered = false;
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: on(Broadcast)Receive(), action=%s", TAG, action);
-
- if (ACTION_SHOW_PIP_MENU.equals(action)) {
- mDelegate.showPictureInPictureMenu();
- } else if (ACTION_CLOSE_PIP.equals(action)) {
- mDelegate.closePip();
- } else if (ACTION_MOVE_PIP.equals(action)) {
- mDelegate.enterPipMovementMenu();
- } else if (ACTION_TOGGLE_EXPANDED_PIP.equals(action)) {
- mDelegate.togglePipExpansion();
- } else if (ACTION_FULLSCREEN.equals(action)) {
- mDelegate.movePipToFullscreen();
- }
+ @Override
+ public void onActionsChanged(int added, int updated, int startIndex) {
+ List<TvPipAction> actions = mTvPipActionsProvider.getActionsList();
+ mPipActions = new Notification.Action[actions.size()];
+ for (int i = 0; i < mPipActions.length; i++) {
+ mPipActions[i] = actions.get(i).toNotificationAction(mContext);
}
+ updateNotificationContent();
}
- interface Delegate {
- void showPictureInPictureMenu();
-
- void closePip();
-
- void enterPipMovementMenu();
-
- void togglePipExpansion();
-
- void movePipToFullscreen();
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java
new file mode 100644
index 000000000000..93b6a908e3f4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip.tv;
+
+import static android.app.Notification.Action.SEMANTIC_ACTION_DELETE;
+import static android.app.Notification.Action.SEMANTIC_ACTION_NONE;
+
+import android.annotation.DrawableRes;
+import android.annotation.NonNull;
+import android.annotation.StringRes;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.graphics.drawable.Icon;
+import android.os.Handler;
+
+import com.android.wm.shell.common.TvWindowMenuActionButton;
+
+/**
+ * A TvPipAction for actions that the system provides, i.e. fullscreen, default close, move,
+ * expand/collapse.
+ */
+public class TvPipSystemAction extends TvPipAction {
+
+ @StringRes
+ private int mTitleResource;
+ @DrawableRes
+ private int mIconResource;
+
+ private final PendingIntent mBroadcastIntent;
+
+ TvPipSystemAction(@ActionType int actionType, @StringRes int title, @DrawableRes int icon,
+ String broadcastAction, @NonNull Context context,
+ SystemActionsHandler systemActionsHandler) {
+ super(actionType, systemActionsHandler);
+ update(title, icon);
+ mBroadcastIntent = TvPipNotificationController.createPendingIntent(context,
+ broadcastAction);
+ }
+
+ void update(@StringRes int title, @DrawableRes int icon) {
+ mTitleResource = title;
+ mIconResource = icon;
+ }
+
+ void populateButton(@NonNull TvWindowMenuActionButton button, Handler mainHandler) {
+ button.setTextAndDescription(mTitleResource);
+ button.setImageResource(mIconResource);
+ button.setEnabled(true);
+ }
+
+ PendingIntent getPendingIntent() {
+ return mBroadcastIntent;
+ }
+
+ @Override
+ Notification.Action toNotificationAction(Context context) {
+ Notification.Action.Builder builder = new Notification.Action.Builder(
+ Icon.createWithResource(context, mIconResource),
+ context.getString(mTitleResource),
+ mBroadcastIntent);
+
+ builder.setSemanticAction(isCloseAction()
+ ? SEMANTIC_ACTION_DELETE : SEMANTIC_ACTION_NONE);
+ builder.setContextual(true);
+ return builder.build();
+ }
+
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 602d0e6c0201..5be29db12b25 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -357,11 +357,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return mMainStage.isActive();
}
- boolean isSplitScreenRunningBackground() {
- return !isSplitScreenVisible() && mMainStageListener.mHasChildren
- && mSideStageListener.mHasChildren;
- }
-
@StageType
int getStageOfTask(int taskId) {
if (mMainStage.containsTask(taskId)) {
@@ -389,7 +384,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
targetStage = stagePosition == mSideStagePosition ? mSideStage : mMainStage;
sideStagePosition = mSideStagePosition;
} else {
- exitSplitIfBackground();
+ // Exit split if it running background.
+ exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
+
targetStage = mSideStage;
sideStagePosition = stagePosition;
}
@@ -685,7 +682,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent,
@Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
RemoteAnimationAdapter adapter, InstanceId instanceId) {
- exitSplitIfBackground();
+ if (!isSplitScreenVisible()) {
+ exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
+ }
+
// Init divider first to make divider leash for remote animation target.
mSplitLayout.init();
mSplitLayout.setDivideRatio(splitRatio);
@@ -1070,7 +1070,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSideStage.removeAllTasks(wct, false /* toTop */);
mMainStage.deactivate(wct, false /* toTop */);
wct.reorder(mRootTaskInfo.token, false /* onTop */);
- wct.setForceTranslucent(mRootTaskInfo.token, true);
wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
onTransitionAnimationComplete();
} else {
@@ -1102,7 +1101,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mMainStage.deactivate(finishedWCT, childrenToTop == mMainStage /* toTop */);
mSideStage.removeAllTasks(finishedWCT, childrenToTop == mSideStage /* toTop */);
finishedWCT.reorder(mRootTaskInfo.token, false /* toTop */);
- finishedWCT.setForceTranslucent(mRootTaskInfo.token, true);
finishedWCT.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
mSyncQueue.queue(finishedWCT);
mSyncQueue.runInSync(at -> {
@@ -1122,13 +1120,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
- /** Exit split screen if it still running background */
- public void exitSplitIfBackground() {
- if (!isSplitScreenRunningBackground()) return;
-
- exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
- }
-
/**
* Overridden by child classes.
*/
@@ -1451,7 +1442,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
void onChildTaskAppeared(StageListenerImpl stageListener, int taskId) {
- if (stageListener == mSideStageListener && isSplitScreenRunningBackground()) {
+ if (stageListener == mSideStageListener && !isSplitScreenVisible() && isSplitActive()
+ && !mIsSplitEntering) {
// Handle entring split case here if split already running background.
if (mIsDropEntering) {
mSplitLayout.resetDividerPosition();
@@ -1459,11 +1451,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitLayout.setDividerAtBorder(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
}
final WindowContainerTransaction wct = new WindowContainerTransaction();
+ mMainStage.reparentTopTask(wct);
mMainStage.evictAllChildren(wct);
mSideStage.evictOtherChildren(wct, taskId);
- mMainStage.reparentTopTask(wct);
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
+ wct.setForceTranslucent(mRootTaskInfo.token, false);
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
@@ -1471,6 +1464,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
mIsDropEntering = false;
} else {
+ mShowDecorImmediately = true;
mSplitLayout.flingDividerToCenter();
}
});
@@ -1499,6 +1493,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (!mainStageVisible) {
wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
true /* setReparentLeafTaskIfRelaunch */);
+ wct.setForceTranslucent(mRootTaskInfo.token, true);
// Both stages are not visible, check if it needs to dismiss split screen.
if (mExitSplitScreenOnHide) {
exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RETURN_HOME);
@@ -1506,6 +1501,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
} else {
wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
false /* setReparentLeafTaskIfRelaunch */);
+ wct.setForceTranslucent(mRootTaskInfo.token, false);
}
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
@@ -1617,8 +1613,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitLayout.flingDividerToDismiss(
mSideStagePosition != SPLIT_POSITION_BOTTOM_OR_RIGHT,
EXIT_REASON_APP_FINISHED);
- } else if (isSplitScreenRunningBackground()) {
- // Do not exit to any stage due to running background.
+ } else if (!isSplitScreenVisible()) {
exitSplitScreen(null /* childrenToTop */, EXIT_REASON_APP_FINISHED);
}
} else if (isSideStage && hasChildren && !mMainStage.isActive()) {
@@ -2355,9 +2350,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (!isSplitScreenVisible()) {
mIsDropEntering = true;
}
- if (isSplitScreenRunningBackground()) {
- // Split running background, log exit first and start new enter request.
- logExit(EXIT_REASON_RECREATE_SPLIT);
+ if (!isSplitScreenVisible()) {
+ // If split running background, exit split first.
+ exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
}
mLogger.enterRequestedByDrag(position, dragSessionId);
}
@@ -2366,9 +2361,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
* Sets info to be logged when splitscreen is next entered.
*/
public void onRequestToSplit(InstanceId sessionId, int enterReason) {
- if (isSplitScreenRunningBackground()) {
- // Split running background, log exit first and start new enter request.
- logExit(EXIT_REASON_RECREATE_SPLIT);
+ if (!isSplitScreenVisible()) {
+ // If split running background, exit split first.
+ exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
}
mLogger.enterRequested(sessionId, enterReason);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index 65da757b1396..9d6711f42efe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -239,7 +239,7 @@ public class TaskSnapshotWindow {
public void resized(ClientWindowFrames frames, boolean reportDraw,
MergedConfiguration mergedConfiguration, InsetsState insetsState,
boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int seqId,
- boolean dragResizing) {
+ int resizeMode) {
final TaskSnapshotWindow snapshot = mOuter.get();
if (snapshot == null) {
return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index afefd5dc6344..42e2b3fadf19 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -46,6 +46,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.Nullable;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -56,7 +57,6 @@ import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
import com.android.wm.shell.transition.Transitions;
import java.util.Optional;
-import java.util.function.Supplier;
/**
* View model for the window decoration with a caption and shadows. Works with
@@ -66,7 +66,6 @@ import java.util.function.Supplier;
public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
private static final String TAG = "CaptionViewModel";
private final CaptionWindowDecoration.Factory mCaptionWindowDecorFactory;
- private final Supplier<InputManager> mInputManagerSupplier;
private final ActivityTaskManager mActivityTaskManager;
private final ShellTaskOrganizer mTaskOrganizer;
private final Context mContext;
@@ -82,7 +81,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
- private EventReceiverFactory mEventReceiverFactory = new EventReceiverFactory();
+ private InputMonitorFactory mInputMonitorFactory;
public CaptionWindowDecorViewModel(
Context context,
@@ -101,10 +100,11 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
syncQueue,
desktopModeController,
new CaptionWindowDecoration.Factory(),
- InputManager::getInstance);
+ new InputMonitorFactory());
}
- public CaptionWindowDecorViewModel(
+ @VisibleForTesting
+ CaptionWindowDecorViewModel(
Context context,
Handler mainHandler,
Choreographer mainChoreographer,
@@ -113,8 +113,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
SyncTransactionQueue syncQueue,
Optional<DesktopModeController> desktopModeController,
CaptionWindowDecoration.Factory captionWindowDecorFactory,
- Supplier<InputManager> inputManagerSupplier) {
-
+ InputMonitorFactory inputMonitorFactory) {
mContext = context;
mMainHandler = mainHandler;
mMainChoreographer = mainChoreographer;
@@ -125,11 +124,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
mDesktopModeController = desktopModeController;
mCaptionWindowDecorFactory = captionWindowDecorFactory;
- mInputManagerSupplier = inputManagerSupplier;
- }
-
- void setEventReceiverFactory(EventReceiverFactory eventReceiverFactory) {
- mEventReceiverFactory = eventReceiverFactory;
+ mInputMonitorFactory = inputMonitorFactory;
}
@Override
@@ -205,7 +200,6 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
decoration.close();
int displayId = taskInfo.displayId;
if (mEventReceiversByDisplay.contains(displayId)) {
- EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
removeTaskFromEventReceiver(displayId);
}
}
@@ -408,12 +402,6 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
}
}
- class EventReceiverFactory {
- EventReceiver create(InputMonitor inputMonitor, InputChannel channel, Looper looper) {
- return new EventReceiver(inputMonitor, channel, looper);
- }
- }
-
/**
* Handle MotionEvents relevant to focused task's caption that don't directly touch it
*
@@ -500,11 +488,11 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
}
private void createInputChannel(int displayId) {
- InputManager inputManager = mInputManagerSupplier.get();
+ InputManager inputManager = InputManager.getInstance();
InputMonitor inputMonitor =
- inputManager.monitorGestureInput("caption-touch", mContext.getDisplayId());
- EventReceiver eventReceiver = mEventReceiverFactory.create(
- inputMonitor, inputMonitor.getInputChannel(), Looper.myLooper());
+ mInputMonitorFactory.create(inputManager, mContext);
+ EventReceiver eventReceiver = new EventReceiver(inputMonitor,
+ inputMonitor.getInputChannel(), Looper.myLooper());
mEventReceiversByDisplay.put(displayId, eventReceiver);
}
@@ -562,4 +550,12 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
mWindowDecorByTaskId.get(taskId).closeHandleMenu();
}
}
+
+ static class InputMonitorFactory {
+ InputMonitor create(InputManager inputManager, Context context) {
+ return inputManager.monitorGestureInput("caption-touch", context.getDisplayId());
+ }
+ }
}
+
+
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 92154968855f..7f85988d1377 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
@@ -33,6 +33,7 @@ import android.view.View;
import android.view.ViewRootImpl;
import android.view.WindowManager;
import android.view.WindowlessWindowManager;
+import android.window.TaskConstants;
import android.window.WindowContainerTransaction;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -195,7 +196,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
.setParent(mTaskSurface)
.build();
- startT.setTrustedOverlay(mDecorationContainerSurface, true);
+ startT.setTrustedOverlay(mDecorationContainerSurface, true)
+ .setLayer(mDecorationContainerSurface,
+ TaskConstants.TASK_CHILD_LAYER_WINDOW_DECORATIONS);
}
final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
@@ -213,8 +216,6 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
outResult.mDecorContainerOffsetX, outResult.mDecorContainerOffsetY)
.setWindowCrop(mDecorationContainerSurface,
outResult.mWidth, outResult.mHeight)
- // TODO(b/244455401): Change the z-order when it's better organized
- .setLayer(mDecorationContainerSurface, mTaskInfo.numActivities + 1)
.show(mDecorationContainerSurface);
// TaskBackgroundSurface
@@ -225,6 +226,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
.setEffectLayer()
.setParent(mTaskSurface)
.build();
+
+ startT.setLayer(mTaskBackgroundSurface, TaskConstants.TASK_CHILD_LAYER_TASK_BACKGROUND);
}
float shadowRadius = loadDimension(resources, params.mShadowRadiusId);
@@ -236,8 +239,6 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
taskBounds.height())
.setShadowRadius(mTaskBackgroundSurface, shadowRadius)
.setColor(mTaskBackgroundSurface, mTmpColor)
- // TODO(b/244455401): Change the z-order when it's better organized
- .setLayer(mTaskBackgroundSurface, -1)
.show(mTaskBackgroundSurface);
// CaptionContainerSurface, CaptionWindowManager
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java
new file mode 100644
index 000000000000..91040e954845
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip.tv;
+
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CLOSE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CUSTOM;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CUSTOM_CLOSE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_EXPAND_COLLAPSE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_FULLSCREEN;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_MOVE;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.PendingIntent;
+import android.app.RemoteAction;
+import android.graphics.drawable.Icon;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.Log;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.pip.PipMediaController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit tests for {@link TvPipActionsProvider}
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class TvPipActionProviderTest extends ShellTestCase {
+ private static final String TAG = TvPipActionProviderTest.class.getSimpleName();
+ private TvPipActionsProvider mActionsProvider;
+
+ @Mock
+ private PipMediaController mMockPipMediaController;
+ @Mock
+ private TvPipActionsProvider.Listener mMockListener;
+ @Mock
+ private TvPipAction.SystemActionsHandler mMockSystemActionsHandler;
+ @Mock
+ private Icon mMockIcon;
+ @Mock
+ private PendingIntent mMockPendingIntent;
+
+ private RemoteAction createRemoteAction(int identifier) {
+ return new RemoteAction(mMockIcon, "" + identifier, "" + identifier, mMockPendingIntent);
+ }
+
+ private List<RemoteAction> createRemoteActions(int numberOfActions) {
+ List<RemoteAction> actions = new ArrayList<>();
+ for (int i = 0; i < numberOfActions; i++) {
+ actions.add(createRemoteAction(i));
+ }
+ return actions;
+ }
+
+ private boolean checkActionsMatch(List<TvPipAction> actions, int[] actionTypes) {
+ for (int i = 0; i < actions.size(); i++) {
+ int type = actions.get(i).getActionType();
+ if (type != actionTypes[i]) {
+ Log.e(TAG, "Action at index " + i + ": found " + type
+ + ", expected " + actionTypes[i]);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mActionsProvider = new TvPipActionsProvider(mContext, mMockPipMediaController,
+ mMockSystemActionsHandler);
+ }
+
+ @Test
+ public void defaultSystemActions_regularPip() {
+ mActionsProvider.updateExpansionEnabled(false);
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE}));
+ }
+
+ @Test
+ public void defaultSystemActions_expandedPip() {
+ mActionsProvider.updateExpansionEnabled(true);
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE}));
+ }
+
+ @Test
+ public void expandedPip_enableExpansion_enable() {
+ // PiP has expanded PiP disabled.
+ mActionsProvider.updateExpansionEnabled(false);
+
+ mActionsProvider.addListener(mMockListener);
+ mActionsProvider.updateExpansionEnabled(true);
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE}));
+ verify(mMockListener).onActionsChanged(/* added= */ 1, /* updated= */ 0, /* index= */ 3);
+ }
+
+ @Test
+ public void expandedPip_enableExpansion_disable() {
+ mActionsProvider.updateExpansionEnabled(true);
+
+ mActionsProvider.addListener(mMockListener);
+ mActionsProvider.updateExpansionEnabled(false);
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE}));
+ verify(mMockListener).onActionsChanged(/* added= */ -1, /* updated= */ 0, /* index= */ 3);
+ }
+
+ @Test
+ public void expandedPip_enableExpansion_AlreadyEnabled() {
+ mActionsProvider.updateExpansionEnabled(true);
+
+ mActionsProvider.addListener(mMockListener);
+ mActionsProvider.updateExpansionEnabled(true);
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE}));
+ }
+
+ @Test
+ public void expandedPip_toggleExpansion() {
+ // PiP has expanded PiP enabled, but is in a collapsed state
+ mActionsProvider.updateExpansionEnabled(true);
+ mActionsProvider.onPipExpansionToggled(/* expanded= */ false);
+
+ mActionsProvider.addListener(mMockListener);
+ mActionsProvider.onPipExpansionToggled(/* expanded= */ true);
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE}));
+ verify(mMockListener).onActionsChanged(0, 1, 3);
+ }
+
+ @Test
+ public void customActions_added() {
+ mActionsProvider.updateExpansionEnabled(false);
+ mActionsProvider.addListener(mMockListener);
+
+ mActionsProvider.setAppActions(createRemoteActions(2), null);
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_MOVE}));
+ verify(mMockListener).onActionsChanged(/* added= */ 2, /* updated= */ 0, /* index= */ 2);
+ }
+
+ @Test
+ public void customActions_replacedMore() {
+ mActionsProvider.updateExpansionEnabled(false);
+ mActionsProvider.setAppActions(createRemoteActions(2), null);
+
+ mActionsProvider.addListener(mMockListener);
+ mActionsProvider.setAppActions(createRemoteActions(3), null);
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_CUSTOM, ACTION_MOVE}));
+ verify(mMockListener).onActionsChanged(/* added= */ 1, /* updated= */ 2, /* index= */ 2);
+ }
+
+ @Test
+ public void customActions_replacedLess() {
+ mActionsProvider.updateExpansionEnabled(false);
+ mActionsProvider.setAppActions(createRemoteActions(2), null);
+
+ mActionsProvider.addListener(mMockListener);
+ mActionsProvider.setAppActions(createRemoteActions(0), null);
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE}));
+ verify(mMockListener).onActionsChanged(/* added= */ -2, /* updated= */ 0, /* index= */ 2);
+ }
+
+ @Test
+ public void customCloseAdded() {
+ mActionsProvider.updateExpansionEnabled(false);
+
+ List<RemoteAction> customActions = new ArrayList<>();
+ mActionsProvider.setAppActions(customActions, null);
+
+ mActionsProvider.addListener(mMockListener);
+ mActionsProvider.setAppActions(customActions, createRemoteAction(0));
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_MOVE}));
+ verify(mMockListener).onActionsChanged(/* added= */ 0, /* updated= */ 1, /* index= */ 1);
+ }
+
+ @Test
+ public void customClose_matchesOtherCustomAction() {
+ mActionsProvider.updateExpansionEnabled(false);
+
+ List<RemoteAction> customActions = createRemoteActions(2);
+ RemoteAction customClose = createRemoteAction(/* id= */ 10);
+ customActions.add(customClose);
+
+ mActionsProvider.addListener(mMockListener);
+ mActionsProvider.setAppActions(customActions, customClose);
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_MOVE}));
+ verify(mMockListener).onActionsChanged(/* added= */ 0, /* updated= */ 1, /* index= */ 1);
+ verify(mMockListener).onActionsChanged(/* added= */ 2, /* updated= */ 0, /* index= */ 2);
+ }
+
+ @Test
+ public void mediaActions_added_whileCustomActionsExist() {
+ mActionsProvider.updateExpansionEnabled(false);
+ mActionsProvider.setAppActions(createRemoteActions(2), null);
+
+ mActionsProvider.addListener(mMockListener);
+ mActionsProvider.onMediaActionsChanged(createRemoteActions(3));
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_MOVE}));
+ verify(mMockListener, times(0)).onActionsChanged(anyInt(), anyInt(), anyInt());
+ }
+
+ @Test
+ public void customActions_removed_whileMediaActionsExist() {
+ mActionsProvider.updateExpansionEnabled(false);
+ mActionsProvider.onMediaActionsChanged(createRemoteActions(2));
+ mActionsProvider.setAppActions(createRemoteActions(3), null);
+
+ mActionsProvider.addListener(mMockListener);
+ mActionsProvider.setAppActions(createRemoteActions(0), null);
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_MOVE}));
+ verify(mMockListener).onActionsChanged(/* added= */ -1, /* updated= */ 2, /* index= */ 2);
+ }
+
+ @Test
+ public void customCloseOnly_mediaActionsShowing() {
+ mActionsProvider.updateExpansionEnabled(false);
+ mActionsProvider.onMediaActionsChanged(createRemoteActions(2));
+
+ mActionsProvider.addListener(mMockListener);
+ mActionsProvider.setAppActions(createRemoteActions(0), createRemoteAction(5));
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_MOVE}));
+ verify(mMockListener).onActionsChanged(/* added= */ 0, /* updated= */ 1, /* index= */ 1);
+ }
+
+ @Test
+ public void customActions_showDisabledActions() {
+ mActionsProvider.updateExpansionEnabled(false);
+
+ List<RemoteAction> customActions = createRemoteActions(2);
+ customActions.get(0).setEnabled(false);
+ mActionsProvider.setAppActions(customActions, null);
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+ ACTION_MOVE}));
+ }
+
+ @Test
+ public void mediaActions_hideDisabledActions() {
+ mActionsProvider.updateExpansionEnabled(false);
+
+ List<RemoteAction> customActions = createRemoteActions(2);
+ customActions.get(0).setEnabled(false);
+ mActionsProvider.onMediaActionsChanged(customActions);
+
+ assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+ new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_MOVE}));
+ }
+
+}
+
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
index ad6fcedd3166..0dbf30d69f75 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
@@ -21,14 +21,15 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
import android.hardware.input.InputManager;
import android.os.Handler;
import android.os.Looper;
@@ -37,9 +38,9 @@ import android.view.Display;
import android.view.InputChannel;
import android.view.InputMonitor;
import android.view.SurfaceControl;
+import android.view.SurfaceView;
import androidx.test.filters.SmallTest;
-import androidx.test.rule.GrantPermissionRule;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
@@ -55,37 +56,28 @@ import org.mockito.Mock;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
-import java.util.function.Supplier;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/** Tests of {@link CaptionWindowDecorViewModel} */
@SmallTest
public class CaptionWindowDecorViewModelTests extends ShellTestCase {
- @Mock private CaptionWindowDecoration mCaptionWindowDecoration;
+ private static final String TAG = "CaptionWindowDecorViewModelTests";
+
+ @Mock private CaptionWindowDecoration mCaptionWindowDecoration;
@Mock private CaptionWindowDecoration.Factory mCaptionWindowDecorFactory;
@Mock private Handler mMainHandler;
-
@Mock private Choreographer mMainChoreographer;
-
@Mock private ShellTaskOrganizer mTaskOrganizer;
-
@Mock private DisplayController mDisplayController;
-
@Mock private SyncTransactionQueue mSyncQueue;
-
@Mock private DesktopModeController mDesktopModeController;
-
@Mock private InputMonitor mInputMonitor;
-
- @Mock private InputChannel mInputChannel;
-
- @Mock private CaptionWindowDecorViewModel.EventReceiverFactory mEventReceiverFactory;
-
- @Mock private CaptionWindowDecorViewModel.EventReceiver mEventReceiver;
-
@Mock private InputManager mInputManager;
+ @Mock private CaptionWindowDecorViewModel.InputMonitorFactory mMockInputMonitorFactory;
private final List<InputManager> mMockInputManagers = new ArrayList<>();
private CaptionWindowDecorViewModel mCaptionWindowDecorViewModel;
@@ -104,44 +96,46 @@ public class CaptionWindowDecorViewModelTests extends ShellTestCase {
mSyncQueue,
Optional.of(mDesktopModeController),
mCaptionWindowDecorFactory,
- new MockObjectSupplier<>(mMockInputManagers, () -> mock(InputManager.class)));
- mCaptionWindowDecorViewModel.setEventReceiverFactory(mEventReceiverFactory);
+ mMockInputMonitorFactory
+ );
doReturn(mCaptionWindowDecoration)
.when(mCaptionWindowDecorFactory)
.create(any(), any(), any(), any(), any(), any(), any(), any());
- when(mInputManager.monitorGestureInput(any(), anyInt())).thenReturn(mInputMonitor);
- when(mEventReceiverFactory.create(any(), any(), any())).thenReturn(mEventReceiver);
- when(mInputMonitor.getInputChannel()).thenReturn(mInputChannel);
+ when(mMockInputMonitorFactory.create(any(), any())).thenReturn(mInputMonitor);
+ // InputChannel cannot be mocked because it passes to InputEventReceiver.
+ final InputChannel[] inputChannels = InputChannel.openInputChannelPair(TAG);
+ inputChannels[0].dispose();
+ when(mInputMonitor.getInputChannel()).thenReturn(inputChannels[1]);
}
@Test
public void testDeleteCaptionOnChangeTransitionWhenNecessary() throws Exception {
- Looper.prepare();
final int taskId = 1;
final ActivityManager.RunningTaskInfo taskInfo =
- createTaskInfo(taskId, WINDOWING_MODE_FREEFORM);
+ createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FREEFORM);
SurfaceControl surfaceControl = mock(SurfaceControl.class);
- final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
- final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
- GrantPermissionRule.grant(android.Manifest.permission.MONITOR_INPUT);
+ runOnMainThread(() -> {
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+
+ mCaptionWindowDecorViewModel.onTaskOpening(taskInfo, surfaceControl, startT, finishT);
- mCaptionWindowDecorViewModel.onTaskOpening(taskInfo, surfaceControl, startT, finishT);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_UNDEFINED);
+ taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
+ mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
+ });
verify(mCaptionWindowDecorFactory)
.create(
- mContext,
- mDisplayController,
- mTaskOrganizer,
- taskInfo,
- surfaceControl,
- mMainHandler,
- mMainChoreographer,
- mSyncQueue);
-
- taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_UNDEFINED);
- taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
- mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
+ mContext,
+ mDisplayController,
+ mTaskOrganizer,
+ taskInfo,
+ surfaceControl,
+ mMainHandler,
+ mMainChoreographer,
+ mSyncQueue);
verify(mCaptionWindowDecoration).close();
}
@@ -149,70 +143,105 @@ public class CaptionWindowDecorViewModelTests extends ShellTestCase {
public void testCreateCaptionOnChangeTransitionWhenNecessary() throws Exception {
final int taskId = 1;
final ActivityManager.RunningTaskInfo taskInfo =
- createTaskInfo(taskId, WINDOWING_MODE_UNDEFINED);
+ createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_UNDEFINED);
SurfaceControl surfaceControl = mock(SurfaceControl.class);
- final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
- final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
- taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
+ runOnMainThread(() -> {
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+ taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
+
+ mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
- mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
- verify(mCaptionWindowDecorFactory, never())
+ mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
+ });
+ verify(mCaptionWindowDecorFactory, times(1))
.create(
- mContext,
- mDisplayController,
- mTaskOrganizer,
- taskInfo,
- surfaceControl,
- mMainHandler,
- mMainChoreographer,
- mSyncQueue);
-
- taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ mContext,
+ mDisplayController,
+ mTaskOrganizer,
+ taskInfo,
+ surfaceControl,
+ mMainHandler,
+ mMainChoreographer,
+ mSyncQueue);
+ }
+
+ @Test
+ public void testCreateAndDisposeEventReceiver() throws Exception {
+ final int taskId = 1;
+ final ActivityManager.RunningTaskInfo taskInfo =
+ createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FREEFORM);
taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
+ runOnMainThread(() -> {
+ SurfaceControl surfaceControl = mock(SurfaceControl.class);
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
- mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
+ mCaptionWindowDecorViewModel.onTaskOpening(taskInfo, surfaceControl, startT, finishT);
- verify(mCaptionWindowDecorFactory)
- .create(
- mContext,
- mDisplayController,
- mTaskOrganizer,
- taskInfo,
- surfaceControl,
- mMainHandler,
- mMainChoreographer,
- mSyncQueue);
+ mCaptionWindowDecorViewModel.destroyWindowDecoration(taskInfo);
+ });
+ verify(mMockInputMonitorFactory).create(any(), any());
+ verify(mInputMonitor).dispose();
}
- private static ActivityManager.RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
+ @Test
+ public void testEventReceiversOnMultipleDisplays() throws Exception {
+ runOnMainThread(() -> {
+ SurfaceView surfaceView = new SurfaceView(mContext);
+ final DisplayManager mDm = mContext.getSystemService(DisplayManager.class);
+ final VirtualDisplay secondaryDisplay = mDm.createVirtualDisplay(
+ "testEventReceiversOnMultipleDisplays", /*width=*/ 400, /*height=*/ 400,
+ /*densityDpi=*/ 320, surfaceView.getHolder().getSurface(),
+ DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
+ int secondaryDisplayId = secondaryDisplay.getDisplay().getDisplayId();
+
+ final int taskId = 1;
+ final ActivityManager.RunningTaskInfo taskInfo =
+ createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FREEFORM);
+ final ActivityManager.RunningTaskInfo secondTaskInfo =
+ createTaskInfo(taskId + 1, secondaryDisplayId, WINDOWING_MODE_FREEFORM);
+ final ActivityManager.RunningTaskInfo thirdTaskInfo =
+ createTaskInfo(taskId + 2, secondaryDisplayId, WINDOWING_MODE_FREEFORM);
+
+ SurfaceControl surfaceControl = mock(SurfaceControl.class);
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+
+ mCaptionWindowDecorViewModel.onTaskOpening(taskInfo, surfaceControl, startT, finishT);
+ mCaptionWindowDecorViewModel.onTaskOpening(secondTaskInfo, surfaceControl,
+ startT, finishT);
+ mCaptionWindowDecorViewModel.onTaskOpening(thirdTaskInfo, surfaceControl,
+ startT, finishT);
+ mCaptionWindowDecorViewModel.destroyWindowDecoration(thirdTaskInfo);
+ mCaptionWindowDecorViewModel.destroyWindowDecoration(taskInfo);
+ });
+ verify(mMockInputMonitorFactory, times(2)).create(any(), any());
+ verify(mInputMonitor, times(1)).dispose();
+ }
+
+ private void runOnMainThread(Runnable r) throws Exception {
+ final Handler mainHandler = new Handler(Looper.getMainLooper());
+ final CountDownLatch latch = new CountDownLatch(1);
+ mainHandler.post(() -> {
+ r.run();
+ latch.countDown();
+ });
+ latch.await(20, TimeUnit.MILLISECONDS);
+ }
+
+ private static ActivityManager.RunningTaskInfo createTaskInfo(int taskId,
+ int displayId, int windowingMode) {
ActivityManager.RunningTaskInfo taskInfo =
new TestRunningTaskInfoBuilder()
- .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setDisplayId(displayId)
.setVisible(true)
.build();
taskInfo.taskId = taskId;
taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
return taskInfo;
}
-
- private static class MockObjectSupplier<T> implements Supplier<T> {
- private final List<T> mObjects;
- private final Supplier<T> mDefaultSupplier;
- private int mNumOfCalls = 0;
-
- private MockObjectSupplier(List<T> objects, Supplier<T> defaultSupplier) {
- mObjects = objects;
- mDefaultSupplier = defaultSupplier;
- }
-
- @Override
- public T get() {
- final T mock =
- mNumOfCalls < mObjects.size() ? mObjects.get(mNumOfCalls)
- : mDefaultSupplier.get();
- ++mNumOfCalls;
- return mock;
- }
- }
}
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index ccd4ed09fa94..4d3f05be367d 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -373,6 +373,8 @@ public final class AudioFormat implements Parcelable {
* Use {@link #ENCODING_DTS_UHD_P2} to transmit DTS UHD Profile 2 (aka DTS:X Profile 2)
* bitstream. */
public static final int ENCODING_DTS_UHD_P2 = 30;
+ /** Audio data format: Direct Stream Digital */
+ public static final int ENCODING_DSD = 31;
/** @hide */
public static String toLogFriendlyEncoding(int enc) {
@@ -437,6 +439,8 @@ public final class AudioFormat implements Parcelable {
return "ENCODING_DTS_HD_MA";
case ENCODING_DTS_UHD_P2:
return "ENCODING_DTS_UHD_P2";
+ case ENCODING_DSD:
+ return "ENCODING_DSD";
default :
return "invalid encoding " + enc;
}
@@ -798,6 +802,7 @@ public final class AudioFormat implements Parcelable {
case ENCODING_DRA:
case ENCODING_DTS_HD_MA:
case ENCODING_DTS_UHD_P2:
+ case ENCODING_DSD:
return true;
default:
return false;
@@ -837,6 +842,7 @@ public final class AudioFormat implements Parcelable {
case ENCODING_DRA:
case ENCODING_DTS_HD_MA:
case ENCODING_DTS_UHD_P2:
+ case ENCODING_DSD:
return true;
default:
return false;
@@ -1211,6 +1217,7 @@ public final class AudioFormat implements Parcelable {
case ENCODING_DRA:
case ENCODING_DTS_HD_MA:
case ENCODING_DTS_UHD_P2:
+ case ENCODING_DSD:
mEncoding = encoding;
break;
case ENCODING_INVALID:
@@ -1441,7 +1448,8 @@ public final class AudioFormat implements Parcelable {
ENCODING_DTS_UHD_P1,
ENCODING_DRA,
ENCODING_DTS_HD_MA,
- ENCODING_DTS_UHD_P2 }
+ ENCODING_DTS_UHD_P2,
+ ENCODING_DSD }
)
@Retention(RetentionPolicy.SOURCE)
public @interface Encoding {}
diff --git a/media/java/android/media/AudioProfile.java b/media/java/android/media/AudioProfile.java
index 5c5f837dd07a..356b765f91c0 100644
--- a/media/java/android/media/AudioProfile.java
+++ b/media/java/android/media/AudioProfile.java
@@ -46,11 +46,17 @@ public class AudioProfile implements Parcelable {
* Encapsulation format is defined in standard IEC 61937.
*/
public static final int AUDIO_ENCAPSULATION_TYPE_IEC61937 = 1;
+ /**
+ * Encapsulation format is PCM, which can be used by other formats that can be wrapped in
+ * a PCM frame, such as DSD(Direct Stream Digital).
+ */
+ public static final int AUDIO_ENCAPSULATION_TYPE_PCM = 2;
/** @hide */
@IntDef({
AUDIO_ENCAPSULATION_TYPE_NONE,
AUDIO_ENCAPSULATION_TYPE_IEC61937,
+ AUDIO_ENCAPSULATION_TYPE_PCM,
})
@Retention(RetentionPolicy.SOURCE)
public @interface EncapsulationType {}
@@ -122,6 +128,7 @@ public class AudioProfile implements Parcelable {
*
* @see #AUDIO_ENCAPSULATION_TYPE_NONE
* @see #AUDIO_ENCAPSULATION_TYPE_IEC61937
+ * @see #AUDIO_ENCAPSULATION_TYPE_PCM
*/
public @EncapsulationType int getEncapsulationType() {
return mEncapsulationType;
diff --git a/media/java/android/media/audio/common/AidlConversion.java b/media/java/android/media/audio/common/AidlConversion.java
index 4cf3b3e95bf0..490809c63e96 100644
--- a/media/java/android/media/audio/common/AidlConversion.java
+++ b/media/java/android/media/audio/common/AidlConversion.java
@@ -614,6 +614,8 @@ public class AidlConversion {
switch (type) {
case android.media.AudioProfile.AUDIO_ENCAPSULATION_TYPE_IEC61937:
return AudioEncapsulationType.IEC61937;
+ case android.media.AudioProfile.AUDIO_ENCAPSULATION_TYPE_PCM:
+ return AudioEncapsulationType.PCM;
case android.media.AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE:
default:
return AudioEncapsulationType.NONE;
@@ -629,6 +631,8 @@ public class AidlConversion {
switch (type) {
case AudioEncapsulationType.IEC61937:
return android.media.AudioProfile.AUDIO_ENCAPSULATION_TYPE_IEC61937;
+ case AudioEncapsulationType.PCM:
+ return android.media.AudioProfile.AUDIO_ENCAPSULATION_TYPE_PCM;
case AudioEncapsulationType.NONE:
default:
return android.media.AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE;
diff --git a/media/java/android/media/projection/IMediaProjectionCallback.aidl b/media/java/android/media/projection/IMediaProjectionCallback.aidl
index f3743d1307e9..2c8de2e4eec1 100644
--- a/media/java/android/media/projection/IMediaProjectionCallback.aidl
+++ b/media/java/android/media/projection/IMediaProjectionCallback.aidl
@@ -19,4 +19,5 @@ package android.media.projection;
/** {@hide} */
oneway interface IMediaProjectionCallback {
void onStop();
+ void onCapturedContentResize(int width, int height);
}
diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl
index 1d58a409718d..a63d02bb1110 100644
--- a/media/java/android/media/projection/IMediaProjectionManager.aidl
+++ b/media/java/android/media/projection/IMediaProjectionManager.aidl
@@ -27,12 +27,32 @@ import android.view.ContentRecordingSession;
interface IMediaProjectionManager {
@UnsupportedAppUsage
boolean hasProjectionPermission(int uid, String packageName);
+
+ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ + ".permission.MANAGE_MEDIA_PROJECTION)")
IMediaProjection createProjection(int uid, String packageName, int type,
boolean permanentGrant);
+
boolean isValidMediaProjection(IMediaProjection projection);
+
+ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ + ".permission.MANAGE_MEDIA_PROJECTION)")
MediaProjectionInfo getActiveProjectionInfo();
+
+ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ + ".permission.MANAGE_MEDIA_PROJECTION)")
void stopActiveProjection();
+
+ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ + ".permission.MANAGE_MEDIA_PROJECTION)")
+ void notifyActiveProjectionCapturedContentResized(int width, int height);
+
+ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ + ".permission.MANAGE_MEDIA_PROJECTION)")
void addCallback(IMediaProjectionWatcherCallback callback);
+
+ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ + ".permission.MANAGE_MEDIA_PROJECTION)")
void removeCallback(IMediaProjectionWatcherCallback callback);
/**
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index ae44fc575f7c..3dfff1fbfc1b 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -234,7 +234,7 @@ public final class MediaProjection {
/**
* Callbacks for the projection session.
*/
- public static abstract class Callback {
+ public abstract static class Callback {
/**
* Called when the MediaProjection session is no longer valid.
* <p>
@@ -243,6 +243,46 @@ public final class MediaProjection {
* </p>
*/
public void onStop() { }
+
+ /**
+ * Indicates the width and height of the captured region in pixels. Called immediately after
+ * capture begins to provide the app with accurate sizing for the stream. Also called
+ * when the region captured in this MediaProjection session is resized.
+ * <p>
+ * The given width and height, in pixels, corresponds to the same width and height that
+ * would be returned from {@link android.view.WindowMetrics#getBounds()}
+ * </p>
+ * <p>
+ * Without the application resizing the {@link VirtualDisplay} (returned from
+ * {@code MediaProjection#createVirtualDisplay}) and output {@link Surface} (provided
+ * to {@code MediaProjection#createVirtualDisplay}), the captured stream will have
+ * letterboxing (black bars) around the recorded content to make up for the
+ * difference in aspect ratio.
+ * </p>
+ * <p>
+ * The application can prevent the letterboxing by overriding this method, and
+ * updating the size of both the {@link VirtualDisplay} and output {@link Surface}:
+ * </p>
+ *
+ * <pre>
+ * &#x40;Override
+ * public String onCapturedContentResize(int width, int height) {
+ * // VirtualDisplay instance from MediaProjection#createVirtualDisplay
+ * virtualDisplay.resize(width, height, dpi);
+ *
+ * // Create a new Surface with the updated size (depending on the application's use
+ * // case, this may be through different APIs - see Surface documentation for
+ * // options).
+ * int texName; // the OpenGL texture object name
+ * SurfaceTexture surfaceTexture = new SurfaceTexture(texName);
+ * surfaceTexture.setDefaultBufferSize(width, height);
+ * Surface surface = new Surface(surfaceTexture);
+ *
+ * // Ensure the VirtualDisplay has the updated Surface to send the capture to.
+ * virtualDisplay.setSurface(surface);
+ * }</pre>
+ */
+ public void onCapturedContentResize(int width, int height) { }
}
private final class MediaProjectionCallback extends IMediaProjectionCallback.Stub {
@@ -252,6 +292,13 @@ public final class MediaProjection {
cbr.onStop();
}
}
+
+ @Override
+ public void onCapturedContentResize(int width, int height) {
+ for (CallbackRecord cbr : mCallbacks.values()) {
+ cbr.onCapturedContentResize(width, height);
+ }
+ }
}
private final static class CallbackRecord {
@@ -271,5 +318,9 @@ public final class MediaProjection {
}
});
}
+
+ public void onCapturedContentResize(int width, int height) {
+ mHandler.post(() -> mCallback.onCapturedContentResize(width, height));
+ }
}
}
diff --git a/media/java/android/media/projection/MediaProjectionConfig.aidl b/media/java/android/media/projection/MediaProjectionConfig.aidl
new file mode 100644
index 000000000000..f78385f43d3d
--- /dev/null
+++ b/media/java/android/media/projection/MediaProjectionConfig.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.projection;
+
+parcelable MediaProjectionConfig;
diff --git a/media/java/android/media/projection/MediaProjectionConfig.java b/media/java/android/media/projection/MediaProjectionConfig.java
new file mode 100644
index 000000000000..29afaa6f4c43
--- /dev/null
+++ b/media/java/android/media/projection/MediaProjectionConfig.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.projection;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+import com.android.internal.util.DataClass;
+
+import java.lang.annotation.Retention;
+
+/**
+ * Configure the {@link MediaProjection} session requested from
+ * {@link MediaProjectionManager#createScreenCaptureIntent(MediaProjectionConfig)}.
+ */
+@DataClass(
+ genEqualsHashCode = true,
+ genAidl = true,
+ genSetters = false,
+ genConstructor = false,
+ genBuilder = false,
+ genToString = false,
+ genHiddenConstDefs = true,
+ genHiddenGetters = true,
+ genConstDefs = false
+)
+public final class MediaProjectionConfig implements Parcelable {
+
+ /**
+ * The user, rather than the host app, determines which region of the display to capture.
+ * @hide
+ */
+ public static final int CAPTURE_REGION_USER_CHOICE = 0;
+
+ /**
+ * The host app specifies a particular display to capture.
+ * @hide
+ */
+ public static final int CAPTURE_REGION_FIXED_DISPLAY = 1;
+
+ /** @hide */
+ @IntDef(prefix = "CAPTURE_REGION_", value = {
+ CAPTURE_REGION_USER_CHOICE,
+ CAPTURE_REGION_FIXED_DISPLAY
+ })
+ @Retention(SOURCE)
+ public @interface CaptureRegion {
+ }
+
+ /**
+ * The particular display to capture. Only used when {@link #getRegionToCapture()} is
+ * {@link #CAPTURE_REGION_FIXED_DISPLAY}; ignored otherwise.
+ *
+ * Only supports values of {@link android.view.Display#DEFAULT_DISPLAY}.
+ */
+ @IntRange(from = DEFAULT_DISPLAY, to = DEFAULT_DISPLAY)
+ private int mDisplayToCapture;
+
+ /**
+ * The region to capture. Defaults to the user's choice.
+ */
+ @CaptureRegion
+ private int mRegionToCapture = CAPTURE_REGION_USER_CHOICE;
+
+ /**
+ * Default instance, with region set to the user's choice.
+ */
+ private MediaProjectionConfig() {
+ }
+
+ /**
+ * Customized instance, with region set to the provided value.
+ */
+ private MediaProjectionConfig(@CaptureRegion int captureRegion) {
+ mRegionToCapture = captureRegion;
+ }
+
+ /**
+ * Returns an instance which restricts the user to capturing a particular display.
+ *
+ * @param displayId The id of the display to capture. Only supports values of
+ * {@link android.view.Display#DEFAULT_DISPLAY}.
+ * @throws IllegalArgumentException If the given {@code displayId} is outside the range of
+ * supported values.
+ */
+ @NonNull
+ public static MediaProjectionConfig createConfigForDisplay(
+ @IntRange(from = DEFAULT_DISPLAY, to = DEFAULT_DISPLAY) int displayId) {
+ if (displayId != DEFAULT_DISPLAY) {
+ throw new IllegalArgumentException(
+ "A config for capturing the non-default display is not supported; requested "
+ + "display id "
+ + displayId);
+ }
+ MediaProjectionConfig config = new MediaProjectionConfig(CAPTURE_REGION_FIXED_DISPLAY);
+ config.mDisplayToCapture = displayId;
+ return config;
+ }
+
+ /**
+ * Returns an instance which allows the user to decide which region is captured. The consent
+ * dialog presents the user with all possible options. If the user selects display capture,
+ * then only the {@link android.view.Display#DEFAULT_DISPLAY} is supported.
+ *
+ * <p>
+ * When passed in to
+ * {@link MediaProjectionManager#createScreenCaptureIntent(MediaProjectionConfig)}, the consent
+ * dialog shown to the user will be the same as if just
+ * {@link MediaProjectionManager#createScreenCaptureIntent()} was invoked.
+ * </p>
+ */
+ @NonNull
+ public static MediaProjectionConfig createConfigForUserChoice() {
+ return new MediaProjectionConfig(CAPTURE_REGION_USER_CHOICE);
+ }
+
+ /**
+ * Returns string representation of the captured region.
+ */
+ @NonNull
+ private static String captureRegionToString(int value) {
+ switch (value) {
+ case CAPTURE_REGION_USER_CHOICE:
+ return "CAPTURE_REGION_USERS_CHOICE";
+ case CAPTURE_REGION_FIXED_DISPLAY:
+ return "CAPTURE_REGION_GIVEN_DISPLAY";
+ default:
+ return Integer.toHexString(value);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "MediaProjectionConfig { "
+ + "displayToCapture = " + mDisplayToCapture + ", "
+ + "regionToCapture = " + captureRegionToString(mRegionToCapture)
+ + " }";
+ }
+
+
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/media/java/android/media/projection/MediaProjectionConfig.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * The particular display to capture. Only used when {@link #getRegionToCapture()} is
+ * {@link #CAPTURE_REGION_FIXED_DISPLAY}; ignored otherwise.
+ *
+ * Only supports values of {@link android.view.Display#DEFAULT_DISPLAY}.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public @IntRange(from = DEFAULT_DISPLAY, to = DEFAULT_DISPLAY) int getDisplayToCapture() {
+ return mDisplayToCapture;
+ }
+
+ /**
+ * The region to capture. Defaults to the user's choice.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public @CaptureRegion int getRegionToCapture() {
+ return mRegionToCapture;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(MediaProjectionConfig other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ MediaProjectionConfig that = (MediaProjectionConfig) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && mDisplayToCapture == that.mDisplayToCapture
+ && mRegionToCapture == that.mRegionToCapture;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + mDisplayToCapture;
+ _hash = 31 * _hash + mRegionToCapture;
+ return _hash;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ dest.writeInt(mDisplayToCapture);
+ dest.writeInt(mRegionToCapture);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ MediaProjectionConfig(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ int displayToCapture = in.readInt();
+ int regionToCapture = in.readInt();
+
+ this.mDisplayToCapture = displayToCapture;
+ AnnotationValidations.validate(
+ IntRange.class, null, mDisplayToCapture,
+ "from", DEFAULT_DISPLAY,
+ "to", DEFAULT_DISPLAY);
+ this.mRegionToCapture = regionToCapture;
+ AnnotationValidations.validate(
+ CaptureRegion.class, null, mRegionToCapture);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<MediaProjectionConfig> CREATOR
+ = new Parcelable.Creator<MediaProjectionConfig>() {
+ @Override
+ public MediaProjectionConfig[] newArray(int size) {
+ return new MediaProjectionConfig[size];
+ }
+
+ @Override
+ public MediaProjectionConfig createFromParcel(@NonNull android.os.Parcel in) {
+ return new MediaProjectionConfig(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1671030124845L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/media/java/android/media/projection/MediaProjectionConfig.java",
+ inputSignatures = "public static final int CAPTURE_REGION_USER_CHOICE\npublic static final int CAPTURE_REGION_FIXED_DISPLAY\nprivate @android.annotation.IntRange int mDisplayToCapture\nprivate @android.media.projection.MediaProjectionConfig.CaptureRegion int mRegionToCapture\npublic static @android.annotation.NonNull android.media.projection.MediaProjectionConfig createConfigForDisplay(int)\npublic static @android.annotation.NonNull android.media.projection.MediaProjectionConfig createConfigForUserChoice()\nprivate static @android.annotation.NonNull java.lang.String captureRegionToString(int)\npublic @java.lang.Override java.lang.String toString()\nclass MediaProjectionConfig extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genSetters=false, genConstructor=false, genBuilder=false, genToString=false, genHiddenConstDefs=true, genHiddenGetters=true, genConstDefs=false)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index b3bd98045164..a4215e68bfef 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -38,6 +38,13 @@ import java.util.Map;
@SystemService(Context.MEDIA_PROJECTION_SERVICE)
public final class MediaProjectionManager {
private static final String TAG = "MediaProjectionManager";
+
+ /**
+ * Intent extra to customize the permission dialog based on the host app's preferences.
+ * @hide
+ */
+ public static final String EXTRA_MEDIA_PROJECTION_CONFIG =
+ "android.media.projection.extra.EXTRA_MEDIA_PROJECTION_CONFIG";
/** @hide */
public static final String EXTRA_APP_TOKEN = "android.media.projection.extra.EXTRA_APP_TOKEN";
/** @hide */
@@ -64,11 +71,13 @@ public final class MediaProjectionManager {
}
/**
- * Returns an Intent that <b>must</b> be passed to startActivityForResult()
- * in order to start screen capture. The activity will prompt
- * the user whether to allow screen capture. The result of this
- * activity should be passed to getMediaProjection.
+ * Returns an {@link Intent} that <b>must</b> be passed to
+ * {@link Activity#startActivityForResult(Intent, int)} (or similar) in order to start screen
+ * capture. The activity will prompt the user whether to allow screen capture. The result of
+ * this activity (received by overriding {@link Activity#onActivityResult(int, int, Intent)})
+ * should be passed to {@link #getMediaProjection(int, Intent)}.
*/
+ @NonNull
public Intent createScreenCaptureIntent() {
Intent i = new Intent();
final ComponentName mediaProjectionPermissionDialogComponent =
@@ -80,6 +89,49 @@ public final class MediaProjectionManager {
}
/**
+ * Returns an {@link Intent} that <b>must</b> be passed to
+ * {@link Activity#startActivityForResult(Intent, int)} (or similar) in order to start screen
+ * capture. Customizes the activity and resulting {@link MediaProjection} session based up
+ * the provided {@code config}. The activity will prompt the user whether to allow screen
+ * capture. The result of this activity (received by overriding
+ * {@link Activity#onActivityResult(int, int, Intent)}) should be passed to
+ * {@link #getMediaProjection(int, Intent)}.
+ *
+ * <p>
+ * If {@link MediaProjectionConfig} was created from:
+ * <li>
+ * <ul>
+ * {@link MediaProjectionConfig#createConfigForDisplay(int)}, then creates an
+ * {@link Intent} for capturing this particular display. The activity limits the user's
+ * choice to just the display specified.
+ * </ul>
+ * <ul>
+ * {@link MediaProjectionConfig#createConfigForUserChoice()}, then creates an
+ * {@link Intent} for deferring which region to capture to the user. This gives the
+ * user the same behaviour as calling {@link #createScreenCaptureIntent()}. The
+ * activity gives the user the choice between
+ * {@link android.view.Display#DEFAULT_DISPLAY}, or a different region.
+ * </ul>
+ * </li>
+ *
+ * @param config Customization for the {@link MediaProjection} that this {@link Intent} requests
+ * the user's consent for.
+ * @return An {@link Intent} requesting the user's consent, specialized based upon the given
+ * configuration.
+ */
+ @NonNull
+ public Intent createScreenCaptureIntent(@NonNull MediaProjectionConfig config) {
+ Intent i = new Intent();
+ final ComponentName mediaProjectionPermissionDialogComponent =
+ ComponentName.unflattenFromString(mContext.getResources()
+ .getString(com.android.internal.R.string
+ .config_mediaProjectionPermissionDialogComponent));
+ i.setComponent(mediaProjectionPermissionDialogComponent);
+ i.putExtra(EXTRA_MEDIA_PROJECTION_CONFIG, config);
+ return i;
+ }
+
+ /**
* Retrieves the {@link MediaProjection} obtained from a successful screen
* capture request. The result code and data from the request are provided
* by overriding {@link Activity#onActivityResult(int, int, Intent)
diff --git a/media/tests/projection/Android.bp b/media/tests/projection/Android.bp
new file mode 100644
index 000000000000..08d950128ce2
--- /dev/null
+++ b/media/tests/projection/Android.bp
@@ -0,0 +1,46 @@
+//########################################################################
+// Build MediaProjectionTests package
+//########################################################################
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "MediaProjectionTests",
+
+ srcs: ["**/*.java"],
+
+ libs: [
+ "android.test.base",
+ "android.test.mock",
+ "android.test.runner",
+ ],
+
+ static_libs: [
+ "androidx.test.runner",
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "mockito-target-extended-minus-junit4",
+ "platform-test-annotations",
+ "testng",
+ "truth-prebuilt",
+ ],
+
+ // Needed for mockito-target-extended-minus-junit4
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
+ ],
+
+ test_suites: ["device-tests"],
+
+ platform_apis: true,
+
+ certificate: "platform",
+}
diff --git a/media/tests/projection/AndroidManifest.xml b/media/tests/projection/AndroidManifest.xml
new file mode 100644
index 000000000000..62f148cfdde1
--- /dev/null
+++ b/media/tests/projection/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ android:installLocation="internalOnly"
+ package="android.media.projection.mediaprojectiontests"
+ android:sharedUserId="com.android.uid.test">
+ <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
+
+ <application android:debuggable="true"
+ android:testOnly="true">
+ <uses-library android:name="android.test.mock" android:required="true"/>
+ <uses-library android:name="android.test.runner"/>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.media.projection.mediaprojectiontests"
+ android:label="MediaProjection package tests"/>
+</manifest>
diff --git a/media/tests/projection/AndroidTest.xml b/media/tests/projection/AndroidTest.xml
new file mode 100644
index 000000000000..f64930a0eb3f
--- /dev/null
+++ b/media/tests/projection/AndroidTest.xml
@@ -0,0 +1,32 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<configuration description="Runs MediaProjection package Tests.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-instrumentation" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="install-arg" value="-t" />
+ <option name="test-file-name" value="MediaProjectionTests.apk" />
+ </target_preparer>
+
+ <option name="test-tag" value="MediaProjectionTests" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="android.media.projection.mediaprojectiontests" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false" />
+ </test>
+</configuration>
diff --git a/media/tests/projection/TEST_MAPPING b/media/tests/projection/TEST_MAPPING
new file mode 100644
index 000000000000..ddb68af10734
--- /dev/null
+++ b/media/tests/projection/TEST_MAPPING
@@ -0,0 +1,13 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {"include-filter": "android.media.projection.mediaprojectiontests"},
+ {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
+ {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+ {"exclude-annotation": "org.junit.Ignore"}
+ ]
+ }
+ ]
+}
diff --git a/media/tests/projection/src/android/media/projection/MediaProjectionConfigTest.java b/media/tests/projection/src/android/media/projection/MediaProjectionConfigTest.java
new file mode 100644
index 000000000000..a30f2e3c7c88
--- /dev/null
+++ b/media/tests/projection/src/android/media/projection/MediaProjectionConfigTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.projection;
+
+import static android.media.projection.MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY;
+import static android.media.projection.MediaProjectionConfig.CAPTURE_REGION_USER_CHOICE;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for the {@link MediaProjectionConfig} class.
+ *
+ * Build/Install/Run:
+ * atest MediaProjectionTests:MediaProjectionConfigTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class MediaProjectionConfigTest {
+ private static final MediaProjectionConfig DISPLAY_CONFIG =
+ MediaProjectionConfig.createConfigForDisplay(DEFAULT_DISPLAY);
+ private static final MediaProjectionConfig USERS_CHOICE_CONFIG =
+ MediaProjectionConfig.createConfigForUserChoice();
+
+ @Test
+ public void testParcelable() {
+ Parcel parcel = Parcel.obtain();
+ DISPLAY_CONFIG.writeToParcel(parcel, 0 /* flags */);
+ parcel.setDataPosition(0);
+ MediaProjectionConfig config = MediaProjectionConfig.CREATOR.createFromParcel(parcel);
+ assertThat(DISPLAY_CONFIG).isEqualTo(config);
+ parcel.recycle();
+ }
+
+ @Test
+ public void testCreateDisplayConfig() {
+ assertThrows(IllegalArgumentException.class,
+ () -> MediaProjectionConfig.createConfigForDisplay(-1));
+ assertThrows(IllegalArgumentException.class,
+ () -> MediaProjectionConfig.createConfigForDisplay(DEFAULT_DISPLAY + 1));
+ assertThat(DISPLAY_CONFIG.getRegionToCapture()).isEqualTo(CAPTURE_REGION_FIXED_DISPLAY);
+ assertThat(DISPLAY_CONFIG.getDisplayToCapture()).isEqualTo(DEFAULT_DISPLAY);
+ }
+
+ @Test
+ public void testCreateUsersChoiceConfig() {
+ assertThat(USERS_CHOICE_CONFIG.getRegionToCapture()).isEqualTo(CAPTURE_REGION_USER_CHOICE);
+ }
+
+ @Test
+ public void testEquals() {
+ assertThat(MediaProjectionConfig.createConfigForUserChoice()).isEqualTo(
+ USERS_CHOICE_CONFIG);
+ assertThat(DISPLAY_CONFIG).isNotEqualTo(USERS_CHOICE_CONFIG);
+ assertThat(MediaProjectionConfig.createConfigForDisplay(DEFAULT_DISPLAY)).isEqualTo(
+ DISPLAY_CONFIG);
+ }
+}
diff --git a/media/tests/projection/src/android/media/projection/MediaProjectionManagerTest.java b/media/tests/projection/src/android/media/projection/MediaProjectionManagerTest.java
new file mode 100644
index 000000000000..a3e49088ac4a
--- /dev/null
+++ b/media/tests/projection/src/android/media/projection/MediaProjectionManagerTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.projection;
+
+import static android.media.projection.MediaProjectionManager.EXTRA_MEDIA_PROJECTION_CONFIG;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.quality.Strictness.LENIENT;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoSession;
+
+/**
+ * Tests for the {@link MediaProjectionManager} class.
+ *
+ * Build/Install/Run:
+ * atest MediaProjectionTests:MediaProjectionManagerTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class MediaProjectionManagerTest {
+ private MediaProjectionManager mMediaProjectionManager;
+ private Context mContext;
+ private MockitoSession mMockingSession;
+ private static final MediaProjectionConfig DISPLAY_CONFIG =
+ MediaProjectionConfig.createConfigForDisplay(DEFAULT_DISPLAY);
+ private static final MediaProjectionConfig USERS_CHOICE_CONFIG =
+ MediaProjectionConfig.createConfigForUserChoice();
+
+ @Before
+ public void setup() throws Exception {
+ mMockingSession =
+ mockitoSession()
+ .initMocks(this)
+ .strictness(LENIENT)
+ .startMocking();
+ mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ spyOn(mContext);
+ mMediaProjectionManager = new MediaProjectionManager(mContext);
+ }
+
+ @After
+ public void teardown() {
+ mMockingSession.finishMocking();
+ }
+
+ @Test
+ public void testCreateScreenCaptureIntent() {
+ final String dialogPackage = "test.package";
+ preparePermissionDialogComponent(dialogPackage);
+
+ final Intent intent = mMediaProjectionManager.createScreenCaptureIntent();
+ assertThat(intent).isNotNull();
+ assertThat(intent.getComponent().getPackageName()).contains(dialogPackage);
+ }
+
+ @Test
+ public void testCreateScreenCaptureIntent_display() {
+ final String dialogPackage = "test.package";
+ preparePermissionDialogComponent(dialogPackage);
+
+ final Intent intent = mMediaProjectionManager.createScreenCaptureIntent(DISPLAY_CONFIG);
+ assertThat(intent).isNotNull();
+ assertThat(intent.getComponent().getPackageName()).contains(dialogPackage);
+ assertThat(intent.getParcelableExtra(EXTRA_MEDIA_PROJECTION_CONFIG,
+ MediaProjectionConfig.class)).isEqualTo(DISPLAY_CONFIG);
+ }
+
+ @Test
+ public void testCreateScreenCaptureIntent_usersChoice() {
+ final String dialogPackage = "test.package";
+ preparePermissionDialogComponent(dialogPackage);
+
+ final Intent intent = mMediaProjectionManager.createScreenCaptureIntent(
+ USERS_CHOICE_CONFIG);
+ assertThat(intent).isNotNull();
+ assertThat(intent.getComponent().getPackageName()).contains(dialogPackage);
+ assertThat(intent.getParcelableExtra(EXTRA_MEDIA_PROJECTION_CONFIG,
+ MediaProjectionConfig.class)).isEqualTo(USERS_CHOICE_CONFIG);
+ }
+
+ private void preparePermissionDialogComponent(@NonNull String dialogPackage) {
+ final Resources mockResources = mock(Resources.class);
+ when(mContext.getResources()).thenReturn(mockResources);
+ doReturn(dialogPackage + "/.TestActivity").when(mockResources).getString(
+ com.android.internal.R.string
+ .config_mediaProjectionPermissionDialogComponent);
+ }
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 267c196addec..e59ba5a20611 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -168,6 +168,11 @@ public class SecureSettings {
Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT,
Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT,
Settings.Secure.NAVIGATION_MODE,
+ Settings.Secure.TRACKPAD_GESTURE_BACK_ENABLED,
+ Settings.Secure.TRACKPAD_GESTURE_HOME_ENABLED,
+ Settings.Secure.TRACKPAD_GESTURE_OVERVIEW_ENABLED,
+ Settings.Secure.TRACKPAD_GESTURE_NOTIFICATION_ENABLED,
+ Settings.Secure.TRACKPAD_GESTURE_QUICK_SWITCH_ENABLED,
Settings.Secure.SKIP_GESTURE_COUNT,
Settings.Secure.SKIP_TOUCH_COUNT,
Settings.Secure.SILENCE_ALARMS_GESTURE_COUNT,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index def9c197a221..58ec349666a8 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -255,6 +255,11 @@ public class SecureSettingsValidators {
new InclusiveFloatRangeValidator(0.0f, Float.MAX_VALUE));
VALIDATORS.put(Secure.BACK_GESTURE_INSET_SCALE_RIGHT,
new InclusiveFloatRangeValidator(0.0f, Float.MAX_VALUE));
+ VALIDATORS.put(Secure.TRACKPAD_GESTURE_BACK_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.TRACKPAD_GESTURE_HOME_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.TRACKPAD_GESTURE_OVERVIEW_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.TRACKPAD_GESTURE_NOTIFICATION_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.TRACKPAD_GESTURE_QUICK_SWITCH_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.AWARE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.SKIP_GESTURE_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.SKIP_TOUCH_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR);
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordancePreviewConstants.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordancePreviewConstants.kt
new file mode 100644
index 000000000000..18e8a962dc70
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordancePreviewConstants.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.shared.quickaffordance.shared.model
+
+object KeyguardQuickAffordancePreviewConstants {
+ const val MESSAGE_ID_SLOT_SELECTED = 1337
+ const val KEY_SLOT_ID = "slot_id"
+ const val KEY_INITIALLY_SELECTED_SLOT_ID = "initially_selected_slot_id"
+}
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml
index e64b586a3e6f..8497ff094c03 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml
@@ -27,6 +27,7 @@
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
+ android:paddingTop="@dimen/keyguard_lock_padding"
android:importantForAccessibility="yes"> <!-- Needed because TYPE_WINDOW_STATE_CHANGED is sent
from this view when bouncer is shown -->
diff --git a/packages/SystemUI/res/drawable/controls_panel_background.xml b/packages/SystemUI/res/drawable/controls_panel_background.xml
new file mode 100644
index 000000000000..9092877fc6fa
--- /dev/null
+++ b/packages/SystemUI/res/drawable/controls_panel_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ ~
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="#1F1F1F" />
+ <corners android:radius="@dimen/notification_corner_radius" />
+</shape> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml b/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml
index 41123c84ded1..18fcebbb65a0 100644
--- a/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml
+++ b/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml
@@ -16,13 +16,53 @@
* limitations under the License.
*/
-->
-<shape
+<selector
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:shape="rectangle">
- <solid android:color="?androidprv:attr/colorSurface"/>
- <size
- android:width="@dimen/keyguard_affordance_width"
- android:height="@dimen/keyguard_affordance_height"/>
- <corners android:radius="@dimen/keyguard_affordance_fixed_radius"/>
-</shape>
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+
+ <item android:state_selected="true">
+ <layer-list>
+ <item
+ android:left="3dp"
+ android:top="3dp"
+ android:right="3dp"
+ android:bottom="3dp">
+ <shape android:shape="oval">
+ <solid android:color="?androidprv:attr/colorSurface"/>
+ <size
+ android:width="@dimen/keyguard_affordance_width"
+ android:height="@dimen/keyguard_affordance_height"/>
+ </shape>
+ </item>
+
+ <item>
+ <shape android:shape="oval">
+ <stroke
+ android:color="@color/control_primary_text"
+ android:width="2dp"/>
+ <size
+ android:width="@dimen/keyguard_affordance_width"
+ android:height="@dimen/keyguard_affordance_height"/>
+ </shape>
+ </item>
+ </layer-list>
+ </item>
+
+ <item>
+ <layer-list>
+ <item
+ android:left="3dp"
+ android:top="3dp"
+ android:right="3dp"
+ android:bottom="3dp">
+ <shape android:shape="oval">
+ <solid android:color="?androidprv:attr/colorSurface"/>
+ <size
+ android:width="@dimen/keyguard_affordance_width"
+ android:height="@dimen/keyguard_affordance_height"/>
+ </shape>
+ </item>
+ </layer-list>
+ </item>
+
+</selector>
diff --git a/packages/SystemUI/res/layout/controls_with_favorites.xml b/packages/SystemUI/res/layout/controls_with_favorites.xml
index 9efad2269463..ee3adba00fe5 100644
--- a/packages/SystemUI/res/layout/controls_with_favorites.xml
+++ b/packages/SystemUI/res/layout/controls_with_favorites.xml
@@ -90,7 +90,7 @@
android:layout_weight="1"
android:layout_marginLeft="@dimen/global_actions_side_margin"
android:layout_marginRight="@dimen/global_actions_side_margin"
- android:background="#ff0000"
+ android:background="@drawable/controls_panel_background"
android:padding="@dimen/global_actions_side_margin"
android:visibility="gone"
/>
diff --git a/packages/SystemUI/res/values-h411dp/dimens.xml b/packages/SystemUI/res/values-h411dp/dimens.xml
new file mode 100644
index 000000000000..6b21353d0e55
--- /dev/null
+++ b/packages/SystemUI/res/values-h411dp/dimens.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<resources>
+ <dimen name="volume_row_slider_height">137dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values-h700dp/dimens.xml b/packages/SystemUI/res/values-h700dp/dimens.xml
index 055308f17776..39777ab56847 100644
--- a/packages/SystemUI/res/values-h700dp/dimens.xml
+++ b/packages/SystemUI/res/values-h700dp/dimens.xml
@@ -17,4 +17,5 @@
<resources>
<!-- Margin above the ambient indication container -->
<dimen name="ambient_indication_container_margin_top">15dp</dimen>
-</resources> \ No newline at end of file
+ <dimen name="volume_row_slider_height">177dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values-h841dp/dimens.xml b/packages/SystemUI/res/values-h841dp/dimens.xml
new file mode 100644
index 000000000000..412da199f6b6
--- /dev/null
+++ b/packages/SystemUI/res/values-h841dp/dimens.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<resources>
+ <dimen name="volume_row_slider_height">237dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 247e44d0f734..6a87630a05f2 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -776,15 +776,11 @@
<integer name="complicationFadeOutDelayMs">200</integer>
<!-- Duration in milliseconds of the dream in un-blur animation. -->
- <integer name="config_dreamOverlayInBlurDurationMs">249</integer>
- <!-- Delay in milliseconds of the dream in un-blur animation. -->
- <integer name="config_dreamOverlayInBlurDelayMs">133</integer>
+ <integer name="config_dreamOverlayInBlurDurationMs">250</integer>
<!-- Duration in milliseconds of the dream in complications fade-in animation. -->
- <integer name="config_dreamOverlayInComplicationsDurationMs">282</integer>
- <!-- Delay in milliseconds of the dream in top complications fade-in animation. -->
- <integer name="config_dreamOverlayInTopComplicationsDelayMs">216</integer>
- <!-- Delay in milliseconds of the dream in bottom complications fade-in animation. -->
- <integer name="config_dreamOverlayInBottomComplicationsDelayMs">299</integer>
+ <integer name="config_dreamOverlayInComplicationsDurationMs">250</integer>
+ <!-- Duration in milliseconds of the y-translation animation when entering a dream -->
+ <integer name="config_dreamOverlayInTranslationYDurationMs">917</integer>
<!-- Icons that don't show in a collapsed non-keyguard statusbar -->
<string-array name="config_collapsed_statusbar_icon_blocklist" translatable="false">
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index ea51a892c060..227c0dd4f667 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -758,6 +758,8 @@
<dimen name="keyguard_affordance_fixed_height">48dp</dimen>
<dimen name="keyguard_affordance_fixed_width">48dp</dimen>
<dimen name="keyguard_affordance_fixed_radius">24dp</dimen>
+ <!-- Amount the button should shake when it's not long-pressed for long enough. -->
+ <dimen name="keyguard_affordance_shake_amplitude">8dp</dimen>
<dimen name="keyguard_affordance_horizontal_offset">32dp</dimen>
<dimen name="keyguard_affordance_vertical_offset">32dp</dimen>
@@ -1528,6 +1530,8 @@
<dimen name="dream_overlay_status_bar_extra_margin">8dp</dimen>
<!-- Dream overlay complications related dimensions -->
+ <!-- The blur radius applied to the dream overlay when entering and exiting dreams -->
+ <dimen name="dream_overlay_anim_blur_radius">50dp</dimen>
<dimen name="dream_overlay_complication_clock_time_text_size">86dp</dimen>
<dimen name="dream_overlay_complication_clock_time_translation_y">28dp</dimen>
<dimen name="dream_overlay_complication_home_controls_padding">28dp</dimen>
@@ -1581,6 +1585,7 @@
<dimen name="dream_overlay_complication_margin">0dp</dimen>
<dimen name="dream_overlay_y_offset">80dp</dimen>
+ <dimen name="dream_overlay_entry_y_offset">40dp</dimen>
<dimen name="dream_overlay_exit_y_offset">40dp</dimen>
<dimen name="status_view_margin_horizontal">0dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index ae1ff1ad5bfd..a2e03c326641 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2859,6 +2859,12 @@
-->
<string name="keyguard_affordance_enablement_dialog_home_instruction_2">&#8226; At least one device is available</string>
+ <!--
+ Error message shown when a button should be pressed and held to activate it, usually shown when
+ the user attempted to tap the button or held it for too short a time. [CHAR LIMIT=32].
+ -->
+ <string name="keyguard_affordance_press_too_short">Press and hold to activate</string>
+
<!-- Text for education page of cancel button to hide the page. [CHAR_LIMIT=NONE] -->
<string name="rear_display_bottom_sheet_cancel">Cancel</string>
<!-- Text for the user to confirm they flipped the device around. [CHAR_LIMIT=NONE] -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index 860c8e3a9f77..7da27b1d6898 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -260,7 +260,8 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey
if (reason != PROMPT_REASON_NONE) {
int promtReasonStringRes = mView.getPromptReasonStringRes(reason);
if (promtReasonStringRes != 0) {
- mMessageAreaController.setMessage(promtReasonStringRes);
+ mMessageAreaController.setMessage(
+ mView.getResources().getString(promtReasonStringRes), false);
}
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 40423cd9ac2c..62babadc45d8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -9,6 +9,7 @@ import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
@@ -43,6 +44,21 @@ public class KeyguardClockSwitch extends RelativeLayout {
public static final int LARGE = 0;
public static final int SMALL = 1;
+ /** Returns a region for the large clock to position itself, based on the given parent. */
+ public static Rect getLargeClockRegion(ViewGroup parent) {
+ int largeClockTopMargin = parent.getResources()
+ .getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin);
+ int targetHeight = parent.getResources()
+ .getDimensionPixelSize(R.dimen.large_clock_text_size) * 2;
+ int top = parent.getHeight() / 2 - targetHeight / 2
+ + largeClockTopMargin / 2;
+ return new Rect(
+ parent.getLeft(),
+ top,
+ parent.getRight(),
+ top + targetHeight);
+ }
+
/**
* Frame for small/large clocks
*/
@@ -129,17 +145,8 @@ public class KeyguardClockSwitch extends RelativeLayout {
}
if (mLargeClockFrame.isLaidOut()) {
- int largeClockTopMargin = getResources()
- .getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin);
- int targetHeight = getResources()
- .getDimensionPixelSize(R.dimen.large_clock_text_size) * 2;
- int top = mLargeClockFrame.getHeight() / 2 - targetHeight / 2
- + largeClockTopMargin / 2;
- mClock.getLargeClock().getEvents().onTargetRegionChanged(new Rect(
- mLargeClockFrame.getLeft(),
- top,
- mLargeClockFrame.getRight(),
- top + targetHeight));
+ mClock.getLargeClock().getEvents().onTargetRegionChanged(
+ getLargeClockRegion(mLargeClockFrame));
}
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 2e9ad5868eba..d1c9a3090860 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -142,8 +142,11 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
}
public void startAppearAnimation() {
- if (TextUtils.isEmpty(mMessageAreaController.getMessage())) {
- mMessageAreaController.setMessage(getInitialMessageResId());
+ if (TextUtils.isEmpty(mMessageAreaController.getMessage())
+ && getInitialMessageResId() != 0) {
+ mMessageAreaController.setMessage(
+ mView.getResources().getString(getInitialMessageResId()),
+ /* animate= */ false);
}
mView.startAppearAnimation();
}
@@ -163,9 +166,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
}
/** Determines the message to show in the bouncer when it first appears. */
- protected int getInitialMessageResId() {
- return 0;
- }
+ protected abstract int getInitialMessageResId();
/** Factory for a {@link KeyguardInputViewController}. */
public static class Factory {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index 5d86ccd5409e..67e3400670ba 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -52,6 +52,7 @@ public class KeyguardPINView extends KeyguardPinBasedInputView {
private int mYTransOffset;
private View mBouncerMessageView;
@DevicePostureInt private int mLastDevicePosture = DEVICE_POSTURE_UNKNOWN;
+ public static final long ANIMATION_DURATION = 650;
public KeyguardPINView(Context context) {
this(context, null);
@@ -181,7 +182,7 @@ public class KeyguardPINView extends KeyguardPinBasedInputView {
if (mAppearAnimator.isRunning()) {
mAppearAnimator.cancel();
}
- mAppearAnimator.setDuration(650);
+ mAppearAnimator.setDuration(ANIMATION_DURATION);
mAppearAnimator.addUpdateListener(animation -> animate(animation.getAnimatedFraction()));
mAppearAnimator.start();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index c985fd7bef82..c1fae9e44bd3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -24,6 +24,7 @@ import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NON_STRONG
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
import android.animation.Animator;
@@ -107,6 +108,8 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView {
return R.string.kg_prompt_reason_timeout_password;
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
return R.string.kg_prompt_reason_timeout_password;
+ case PROMPT_REASON_TRUSTAGENT_EXPIRED:
+ return R.string.kg_prompt_reason_timeout_password;
case PROMPT_REASON_NONE:
return 0;
default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 571d2740773d..0c1748982e51 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -313,6 +313,9 @@ public class KeyguardPatternViewController
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
break;
+ case PROMPT_REASON_TRUSTAGENT_EXPIRED:
+ mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
+ break;
case PROMPT_REASON_NONE:
break;
default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index c46e33d9fd53..0a91150e6c39 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -22,6 +22,7 @@ import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NON_STRONG
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
import android.animation.Animator;
@@ -123,6 +124,8 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView
return R.string.kg_prompt_reason_timeout_pin;
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
return R.string.kg_prompt_reason_timeout_pin;
+ case PROMPT_REASON_TRUSTAGENT_EXPIRED:
+ return R.string.kg_prompt_reason_timeout_pin;
case PROMPT_REASON_NONE:
return 0;
default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index f7423ed12e68..8011efdc1ae7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -139,4 +139,9 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB
super.startErrorAnimation();
mView.startErrorAnimation();
}
+
+ @Override
+ protected int getInitialMessageResId() {
+ return R.string.keyguard_enter_your_pin;
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index f51ac325c9c1..35b2db27d879 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -74,9 +74,4 @@ public class KeyguardPinViewController
return mView.startDisappearAnimation(
mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable);
}
-
- @Override
- protected int getInitialMessageResId() {
- return R.string.keyguard_enter_your_pin;
- }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 8f3484a0c99b..5d7a6f122e69 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -36,8 +36,11 @@ import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY;
import static java.lang.Integer.max;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.admin.DevicePolicyManager;
@@ -967,11 +970,23 @@ public class KeyguardSecurityContainer extends ConstraintLayout {
}
mUserSwitcherViewGroup.setAlpha(0f);
- ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mUserSwitcherViewGroup, View.ALPHA,
- 1f);
- alphaAnim.setInterpolator(Interpolators.ALPHA_IN);
- alphaAnim.setDuration(500);
- alphaAnim.start();
+ ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
+ int yTrans = mView.getResources().getDimensionPixelSize(R.dimen.pin_view_trans_y_entry);
+ animator.setInterpolator(Interpolators.STANDARD_DECELERATE);
+ animator.setDuration(650);
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mUserSwitcherViewGroup.setAlpha(1f);
+ mUserSwitcherViewGroup.setTranslationY(0f);
+ }
+ });
+ animator.addUpdateListener(animation -> {
+ float value = (float) animation.getAnimatedValue();
+ mUserSwitcherViewGroup.setAlpha(value);
+ mUserSwitcherViewGroup.setTranslationY(yTrans - yTrans * value);
+ });
+ animator.start();
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
index ac00e9453c97..67d77e53738a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
@@ -61,6 +61,12 @@ public interface KeyguardSecurityView {
int PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT = 7;
/**
+ * Some auth is required because the trustagent expired either from timeout or manually by the
+ * user
+ */
+ int PROMPT_REASON_TRUSTAGENT_EXPIRED = 8;
+
+ /**
* Reset the view and prepare to take input. This should do things like clearing the
* password or pattern and clear error messages.
*/
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
index a5c8c7881e3b..39b567fd21b9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
@@ -156,5 +156,10 @@ public class KeyguardSecurityViewFlipperController
@Override
public void onStartingToHide() {
}
+
+ @Override
+ protected int getInitialMessageResId() {
+ return 0;
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 84ef505c0af9..3a592a91873c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1644,7 +1644,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
@Override
public void onAuthenticationFailed() {
- requestActiveUnlock(
+ requestActiveUnlockDismissKeyguard(
ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL,
"fingerprintFailure");
handleFingerprintAuthFailed();
@@ -2576,6 +2576,18 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
/**
+ * Attempts to trigger active unlock from trust agent with a request to dismiss the keyguard.
+ */
+ public void requestActiveUnlockDismissKeyguard(
+ @NonNull ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN requestOrigin,
+ String extraReason
+ ) {
+ requestActiveUnlock(
+ requestOrigin,
+ extraReason + "-dismissKeyguard", true);
+ }
+
+ /**
* Whether the UDFPS bouncer is showing.
*/
public void setUdfpsBouncerShowing(boolean showing) {
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
index e2ef2477c836..58d40d349dec 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
@@ -28,7 +28,6 @@ import android.os.RemoteException
import android.os.UserHandle
import android.util.Log
import android.view.WindowManager
-import androidx.annotation.VisibleForTesting
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.ActivityIntentHelper
import com.android.systemui.dagger.qualifiers.Main
@@ -83,7 +82,7 @@ class CameraGestureHelper @Inject constructor(
*/
fun launchCamera(source: Int) {
val intent: Intent = getStartCameraIntent()
- intent.putExtra(EXTRA_CAMERA_LAUNCH_SOURCE, source)
+ intent.putExtra(CameraIntents.EXTRA_LAUNCH_SOURCE, source)
val wouldLaunchResolverActivity = activityIntentHelper.wouldLaunchResolverActivity(
intent, KeyguardUpdateMonitor.getCurrentUser()
)
@@ -149,9 +148,4 @@ class CameraGestureHelper @Inject constructor(
cameraIntents.getInsecureCameraIntent()
}
}
-
- companion object {
- @VisibleForTesting
- const val EXTRA_CAMERA_LAUNCH_SOURCE = "com.android.systemui.camera_launch_source"
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
index f8a20023e47a..867faf9843fe 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
@@ -29,6 +29,7 @@ class CameraIntents {
MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE
val DEFAULT_INSECURE_CAMERA_INTENT_ACTION =
MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA
+ const val EXTRA_LAUNCH_SOURCE = "com.android.systemui.camera_launch_source"
@JvmStatic
fun getOverrideCameraPackage(context: Context): String? {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt
index 4aa597ef3d28..8d0edf829416 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt
@@ -50,7 +50,12 @@ object ControlsAnimations {
* Setup an activity to handle enter/exit animations. [view] should be the root of the content.
* Fade and translate together.
*/
- fun observerForAnimations(view: ViewGroup, window: Window, intent: Intent): LifecycleObserver {
+ fun observerForAnimations(
+ view: ViewGroup,
+ window: Window,
+ intent: Intent,
+ animateY: Boolean = true
+ ): LifecycleObserver {
return object : LifecycleObserver {
var showAnimation = intent.getBooleanExtra(ControlsUiController.EXTRA_ANIMATE, false)
@@ -61,8 +66,12 @@ object ControlsAnimations {
view.transitionAlpha = 0.0f
if (translationY == -1f) {
- translationY = view.context.resources.getDimensionPixelSize(
- R.dimen.global_actions_controls_y_translation).toFloat()
+ if (animateY) {
+ translationY = view.context.resources.getDimensionPixelSize(
+ R.dimen.global_actions_controls_y_translation).toFloat()
+ } else {
+ translationY = 0f
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
index 5d611c4c8212..d8d8c0ead06a 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
@@ -70,7 +70,8 @@ class ControlsActivity @Inject constructor(
ControlsAnimations.observerForAnimations(
requireViewById<ViewGroup>(R.id.control_detail_root),
window,
- intent
+ intent,
+ !featureFlags.isEnabled(Flags.USE_APP_PANELS)
)
)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index fb678aa420bf..1e3e5cd1c31c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -186,7 +186,7 @@ class ControlsUiControllerImpl @Inject constructor (
val allStructures = controlsController.get().getFavorites()
val selected = getPreferredSelectedItem(allStructures)
val anyPanels = controlsListingController.get().getCurrentServices()
- .none { it.panelActivity != null }
+ .any { it.panelActivity != null }
return if (controlsController.get().addSeedingFavoritesCallback(onSeedingComplete)) {
ControlsActivity::class.java
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
index 7143be298a9d..f5764c2fdc04 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
@@ -24,6 +24,10 @@ import android.app.PendingIntent
import android.content.ComponentName
import android.content.Context
import android.content.Intent
+import android.graphics.Color
+import android.graphics.drawable.ShapeDrawable
+import android.graphics.drawable.shapes.RoundRectShape
+import com.android.systemui.R
import com.android.systemui.util.boundsOnScreen
import com.android.wm.shell.TaskView
import java.util.concurrent.Executor
@@ -64,6 +68,16 @@ class PanelTaskViewController(
options.taskAlwaysOnTop = true
taskView.post {
+ val roundedCorner =
+ activityContext.resources.getDimensionPixelSize(
+ R.dimen.notification_corner_radius
+ )
+ val radii = FloatArray(8) { roundedCorner.toFloat() }
+ taskView.background =
+ ShapeDrawable(RoundRectShape(radii, null, null)).apply {
+ setTint(Color.TRANSPARENT)
+ }
+ taskView.clipToOutline = true
taskView.startActivity(
pendingIntent,
fillInIntent,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index 0087c8439370..9b8ef71882e9 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -21,11 +21,12 @@ import android.animation.AnimatorSet
import android.animation.ValueAnimator
import android.view.View
import android.view.animation.Interpolator
-import androidx.annotation.FloatRange
import androidx.core.animation.doOnEnd
import com.android.systemui.animation.Interpolators
import com.android.systemui.dreams.complication.ComplicationHostViewController
import com.android.systemui.dreams.complication.ComplicationLayoutParams
+import com.android.systemui.dreams.complication.ComplicationLayoutParams.POSITION_BOTTOM
+import com.android.systemui.dreams.complication.ComplicationLayoutParams.POSITION_TOP
import com.android.systemui.dreams.complication.ComplicationLayoutParams.Position
import com.android.systemui.dreams.dagger.DreamOverlayModule
import com.android.systemui.statusbar.BlurUtils
@@ -41,16 +42,15 @@ constructor(
private val mComplicationHostViewController: ComplicationHostViewController,
private val mStatusBarViewController: DreamOverlayStatusBarViewController,
private val mOverlayStateController: DreamOverlayStateController,
+ @Named(DreamOverlayModule.DREAM_BLUR_RADIUS) private val mDreamBlurRadius: Int,
@Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DURATION)
private val mDreamInBlurAnimDurationMs: Long,
- @Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DELAY)
- private val mDreamInBlurAnimDelayMs: Long,
@Named(DreamOverlayModule.DREAM_IN_COMPLICATIONS_ANIMATION_DURATION)
private val mDreamInComplicationsAnimDurationMs: Long,
- @Named(DreamOverlayModule.DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY)
- private val mDreamInTopComplicationsAnimDelayMs: Long,
- @Named(DreamOverlayModule.DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY)
- private val mDreamInBottomComplicationsAnimDelayMs: Long,
+ @Named(DreamOverlayModule.DREAM_IN_TRANSLATION_Y_DISTANCE)
+ private val mDreamInTranslationYDistance: Int,
+ @Named(DreamOverlayModule.DREAM_IN_TRANSLATION_Y_DURATION)
+ private val mDreamInTranslationYDurationMs: Long,
@Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DISTANCE)
private val mDreamOutTranslationYDistance: Int,
@Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DURATION)
@@ -74,7 +74,7 @@ constructor(
*/
private var mCurrentAlphaAtPosition = mutableMapOf<Int, Float>()
- @FloatRange(from = 0.0, to = 1.0) private var mBlurProgress: Float = 0f
+ private var mCurrentBlurRadius: Float = 0f
/** Starts the dream content and dream overlay entry animations. */
@JvmOverloads
@@ -86,25 +86,23 @@ constructor(
playTogether(
blurAnimator(
view = view,
- from = 1f,
- to = 0f,
+ fromBlurRadius = mDreamBlurRadius.toFloat(),
+ toBlurRadius = 0f,
durationMs = mDreamInBlurAnimDurationMs,
- delayMs = mDreamInBlurAnimDelayMs
+ interpolator = Interpolators.EMPHASIZED_DECELERATE
),
alphaAnimator(
from = 0f,
to = 1f,
durationMs = mDreamInComplicationsAnimDurationMs,
- delayMs = mDreamInTopComplicationsAnimDelayMs,
- position = ComplicationLayoutParams.POSITION_TOP
+ interpolator = Interpolators.LINEAR
+ ),
+ translationYAnimator(
+ from = mDreamInTranslationYDistance.toFloat(),
+ to = 0f,
+ durationMs = mDreamInTranslationYDurationMs,
+ interpolator = Interpolators.EMPHASIZED_DECELERATE
),
- alphaAnimator(
- from = 0f,
- to = 1f,
- durationMs = mDreamInComplicationsAnimDurationMs,
- delayMs = mDreamInBottomComplicationsAnimDelayMs,
- position = ComplicationLayoutParams.POSITION_BOTTOM
- )
)
doOnEnd {
mAnimator = null
@@ -130,47 +128,48 @@ constructor(
view = view,
// Start the blurring wherever the entry animation ended, in
// case it was cancelled early.
- from = mBlurProgress,
- to = 1f,
- durationMs = mDreamOutBlurDurationMs
+ fromBlurRadius = mCurrentBlurRadius,
+ toBlurRadius = mDreamBlurRadius.toFloat(),
+ durationMs = mDreamOutBlurDurationMs,
+ interpolator = Interpolators.EMPHASIZED_ACCELERATE
),
translationYAnimator(
from = 0f,
to = mDreamOutTranslationYDistance.toFloat(),
durationMs = mDreamOutTranslationYDurationMs,
delayMs = mDreamOutTranslationYDelayBottomMs,
- position = ComplicationLayoutParams.POSITION_BOTTOM,
- animInterpolator = Interpolators.EMPHASIZED_ACCELERATE
+ positions = POSITION_BOTTOM,
+ interpolator = Interpolators.EMPHASIZED_ACCELERATE
),
translationYAnimator(
from = 0f,
to = mDreamOutTranslationYDistance.toFloat(),
durationMs = mDreamOutTranslationYDurationMs,
delayMs = mDreamOutTranslationYDelayTopMs,
- position = ComplicationLayoutParams.POSITION_TOP,
- animInterpolator = Interpolators.EMPHASIZED_ACCELERATE
+ positions = POSITION_TOP,
+ interpolator = Interpolators.EMPHASIZED_ACCELERATE
),
alphaAnimator(
from =
mCurrentAlphaAtPosition.getOrDefault(
- key = ComplicationLayoutParams.POSITION_BOTTOM,
+ key = POSITION_BOTTOM,
defaultValue = 1f
),
to = 0f,
durationMs = mDreamOutAlphaDurationMs,
delayMs = mDreamOutAlphaDelayBottomMs,
- position = ComplicationLayoutParams.POSITION_BOTTOM
+ positions = POSITION_BOTTOM
),
alphaAnimator(
from =
mCurrentAlphaAtPosition.getOrDefault(
- key = ComplicationLayoutParams.POSITION_TOP,
+ key = POSITION_TOP,
defaultValue = 1f
),
to = 0f,
durationMs = mDreamOutAlphaDurationMs,
delayMs = mDreamOutAlphaDelayTopMs,
- position = ComplicationLayoutParams.POSITION_TOP
+ positions = POSITION_TOP
)
)
doOnEnd {
@@ -194,20 +193,21 @@ constructor(
private fun blurAnimator(
view: View,
- from: Float,
- to: Float,
+ fromBlurRadius: Float,
+ toBlurRadius: Float,
durationMs: Long,
- delayMs: Long = 0
+ delayMs: Long = 0,
+ interpolator: Interpolator = Interpolators.LINEAR
): Animator {
- return ValueAnimator.ofFloat(from, to).apply {
+ return ValueAnimator.ofFloat(fromBlurRadius, toBlurRadius).apply {
duration = durationMs
startDelay = delayMs
- interpolator = Interpolators.LINEAR
+ this.interpolator = interpolator
addUpdateListener { animator: ValueAnimator ->
- mBlurProgress = animator.animatedValue as Float
+ mCurrentBlurRadius = animator.animatedValue as Float
mBlurUtils.applyBlur(
viewRootImpl = view.viewRootImpl,
- radius = mBlurUtils.blurRadiusOfRatio(mBlurProgress).toInt(),
+ radius = mCurrentBlurRadius.toInt(),
opaque = false
)
}
@@ -218,18 +218,24 @@ constructor(
from: Float,
to: Float,
durationMs: Long,
- delayMs: Long,
- @Position position: Int
+ delayMs: Long = 0,
+ @Position positions: Int = POSITION_TOP or POSITION_BOTTOM,
+ interpolator: Interpolator = Interpolators.LINEAR
): Animator {
return ValueAnimator.ofFloat(from, to).apply {
duration = durationMs
startDelay = delayMs
- interpolator = Interpolators.LINEAR
+ this.interpolator = interpolator
addUpdateListener { va: ValueAnimator ->
- setElementsAlphaAtPosition(
- alpha = va.animatedValue as Float,
- position = position,
- fadingOut = to < from
+ ComplicationLayoutParams.iteratePositions(
+ { position: Int ->
+ setElementsAlphaAtPosition(
+ alpha = va.animatedValue as Float,
+ position = position,
+ fadingOut = to < from
+ )
+ },
+ positions
)
}
}
@@ -239,16 +245,21 @@ constructor(
from: Float,
to: Float,
durationMs: Long,
- delayMs: Long,
- @Position position: Int,
- animInterpolator: Interpolator
+ delayMs: Long = 0,
+ @Position positions: Int = POSITION_TOP or POSITION_BOTTOM,
+ interpolator: Interpolator = Interpolators.LINEAR
): Animator {
return ValueAnimator.ofFloat(from, to).apply {
duration = durationMs
startDelay = delayMs
- interpolator = animInterpolator
+ this.interpolator = interpolator
addUpdateListener { va: ValueAnimator ->
- setElementsTranslationYAtPosition(va.animatedValue as Float, position)
+ ComplicationLayoutParams.iteratePositions(
+ { position: Int ->
+ setElementsTranslationYAtPosition(va.animatedValue as Float, position)
+ },
+ positions
+ )
}
}
}
@@ -263,7 +274,7 @@ constructor(
CrossFadeHelper.fadeIn(view, alpha, /* remap= */ false)
}
}
- if (position == ComplicationLayoutParams.POSITION_TOP) {
+ if (position == POSITION_TOP) {
mStatusBarViewController.setFadeAmount(alpha, fadingOut)
}
}
@@ -273,7 +284,7 @@ constructor(
mComplicationHostViewController.getViewsAtPosition(position).forEach { v ->
v.translationY = translationY
}
- if (position == ComplicationLayoutParams.POSITION_TOP) {
+ if (position == POSITION_TOP) {
mStatusBarViewController.setTranslationY(translationY)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
index 1755cb92da70..99e19fc96d8f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
@@ -251,9 +251,17 @@ public class ComplicationLayoutParams extends ViewGroup.LayoutParams {
* position specified for this {@link ComplicationLayoutParams}.
*/
public void iteratePositions(Consumer<Integer> consumer) {
+ iteratePositions(consumer, mPosition);
+ }
+
+ /**
+ * Iterates over the defined positions and invokes the specified {@link Consumer} for each
+ * position specified by the given {@code position}.
+ */
+ public static void iteratePositions(Consumer<Integer> consumer, @Position int position) {
for (int currentPosition = FIRST_POSITION; currentPosition <= LAST_POSITION;
currentPosition <<= 1) {
- if ((mPosition & currentPosition) == currentPosition) {
+ if ((position & currentPosition) == currentPosition) {
consumer.accept(currentPosition);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
index ee0051220787..1065b94508f8 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
@@ -136,8 +136,15 @@ public class DreamHomeControlsComplication implements Complication {
final boolean hasFavorites = mControlsComponent.getControlsController()
.map(c -> !c.getFavorites().isEmpty())
.orElse(false);
+ boolean hasPanels = false;
+ for (int i = 0; i < controlsServices.size(); i++) {
+ if (controlsServices.get(i).getPanelActivity() != null) {
+ hasPanels = true;
+ break;
+ }
+ }
final ControlsComponent.Visibility visibility = mControlsComponent.getVisibility();
- return hasFavorites && visibility != UNAVAILABLE;
+ return (hasFavorites || hasPanels) && visibility != UNAVAILABLE;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index 448538193eaf..4aa46d4e4b5a 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -50,14 +50,14 @@ public abstract class DreamOverlayModule {
public static final String BURN_IN_PROTECTION_UPDATE_INTERVAL =
"burn_in_protection_update_interval";
public static final String MILLIS_UNTIL_FULL_JITTER = "millis_until_full_jitter";
+ public static final String DREAM_BLUR_RADIUS = "DREAM_BLUR_RADIUS";
public static final String DREAM_IN_BLUR_ANIMATION_DURATION = "dream_in_blur_anim_duration";
- public static final String DREAM_IN_BLUR_ANIMATION_DELAY = "dream_in_blur_anim_delay";
public static final String DREAM_IN_COMPLICATIONS_ANIMATION_DURATION =
"dream_in_complications_anim_duration";
- public static final String DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY =
- "dream_in_top_complications_anim_delay";
- public static final String DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY =
- "dream_in_bottom_complications_anim_delay";
+ public static final String DREAM_IN_TRANSLATION_Y_DISTANCE =
+ "dream_in_complications_translation_y";
+ public static final String DREAM_IN_TRANSLATION_Y_DURATION =
+ "dream_in_complications_translation_y_duration";
public static final String DREAM_OUT_TRANSLATION_Y_DISTANCE =
"dream_out_complications_translation_y";
public static final String DREAM_OUT_TRANSLATION_Y_DURATION =
@@ -134,21 +134,21 @@ public abstract class DreamOverlayModule {
}
/**
- * Duration in milliseconds of the dream in un-blur animation.
+ * The blur radius applied to the dream overlay at dream entry and exit.
*/
@Provides
- @Named(DREAM_IN_BLUR_ANIMATION_DURATION)
- static long providesDreamInBlurAnimationDuration(@Main Resources resources) {
- return (long) resources.getInteger(R.integer.config_dreamOverlayInBlurDurationMs);
+ @Named(DREAM_BLUR_RADIUS)
+ static int providesDreamBlurRadius(@Main Resources resources) {
+ return resources.getDimensionPixelSize(R.dimen.dream_overlay_anim_blur_radius);
}
/**
- * Delay in milliseconds of the dream in un-blur animation.
+ * Duration in milliseconds of the dream in un-blur animation.
*/
@Provides
- @Named(DREAM_IN_BLUR_ANIMATION_DELAY)
- static long providesDreamInBlurAnimationDelay(@Main Resources resources) {
- return (long) resources.getInteger(R.integer.config_dreamOverlayInBlurDelayMs);
+ @Named(DREAM_IN_BLUR_ANIMATION_DURATION)
+ static long providesDreamInBlurAnimationDuration(@Main Resources resources) {
+ return (long) resources.getInteger(R.integer.config_dreamOverlayInBlurDurationMs);
}
/**
@@ -161,22 +161,23 @@ public abstract class DreamOverlayModule {
}
/**
- * Delay in milliseconds of the dream in top complications fade-in animation.
+ * Provides the number of pixels to translate complications when entering a dream.
*/
@Provides
- @Named(DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY)
- static long providesDreamInTopComplicationsAnimationDelay(@Main Resources resources) {
- return (long) resources.getInteger(R.integer.config_dreamOverlayInTopComplicationsDelayMs);
+ @Named(DREAM_IN_TRANSLATION_Y_DISTANCE)
+ @DreamOverlayComponent.DreamOverlayScope
+ static int providesDreamInComplicationsTranslationY(@Main Resources resources) {
+ return resources.getDimensionPixelSize(R.dimen.dream_overlay_entry_y_offset);
}
/**
- * Delay in milliseconds of the dream in bottom complications fade-in animation.
+ * Provides the duration in ms of the y-translation when dream enters.
*/
@Provides
- @Named(DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY)
- static long providesDreamInBottomComplicationsAnimationDelay(@Main Resources resources) {
- return (long) resources.getInteger(
- R.integer.config_dreamOverlayInBottomComplicationsDelayMs);
+ @Named(DREAM_IN_TRANSLATION_Y_DURATION)
+ @DreamOverlayComponent.DreamOverlayScope
+ static long providesDreamInComplicationsTranslationYDuration(@Main Resources resources) {
+ return (long) resources.getInteger(R.integer.config_dreamOverlayInTranslationYDurationMs);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt
index 4ae37c51f278..cbcede023708 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt
@@ -21,14 +21,18 @@ import android.content.ContentProvider
import android.content.ContentValues
import android.content.Context
import android.content.UriMatcher
+import android.content.pm.PackageManager
import android.content.pm.ProviderInfo
import android.database.Cursor
import android.database.MatrixCursor
import android.net.Uri
+import android.os.Binder
+import android.os.Bundle
import android.util.Log
import com.android.systemui.SystemUIAppComponentFactoryBase
import com.android.systemui.SystemUIAppComponentFactoryBase.ContextAvailableCallback
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
+import com.android.systemui.keyguard.ui.preview.KeyguardRemotePreviewManager
import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract
import javax.inject.Inject
import kotlinx.coroutines.runBlocking
@@ -37,6 +41,7 @@ class KeyguardQuickAffordanceProvider :
ContentProvider(), SystemUIAppComponentFactoryBase.ContextInitializer {
@Inject lateinit var interactor: KeyguardQuickAffordanceInteractor
+ @Inject lateinit var previewManager: KeyguardRemotePreviewManager
private lateinit var contextAvailableCallback: ContextAvailableCallback
@@ -149,6 +154,21 @@ class KeyguardQuickAffordanceProvider :
return deleteSelection(uri, selectionArgs)
}
+ override fun call(method: String, arg: String?, extras: Bundle?): Bundle? {
+ return if (
+ requireContext()
+ .checkPermission(
+ android.Manifest.permission.BIND_WALLPAPER,
+ Binder.getCallingPid(),
+ Binder.getCallingUid(),
+ ) == PackageManager.PERMISSION_GRANTED
+ ) {
+ previewManager.preview(extras)
+ } else {
+ null
+ }
+ }
+
private fun insertSelection(values: ContentValues?): Uri? {
if (values == null) {
throw IllegalArgumentException("Cannot insert selection, no values passed in!")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index d6418d0829a3..923413633351 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -25,6 +25,7 @@ import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NAV_BA
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_OCCLUSION;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_UNLOCK_ANIMATION;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
@@ -142,12 +143,12 @@ import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.util.DeviceConfigProxy;
+import dagger.Lazy;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.concurrent.Executor;
-import dagger.Lazy;
-
/**
* Mediates requests related to the keyguard. This includes queries about the
* state of the keyguard, power management events that effect whether the keyguard
@@ -822,6 +823,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
} else if (trustAgentsEnabled
&& (strongAuth & SOME_AUTH_REQUIRED_AFTER_USER_REQUEST) != 0) {
return KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
+ } else if (trustAgentsEnabled
+ && (strongAuth & SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED) != 0) {
+ return KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED;
} else if (any && ((strongAuth & STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) != 0
|| mUpdateMonitor.isFingerprintLockedOut())) {
return KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
index 2558fab216a0..394426df5552 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
@@ -130,6 +130,7 @@ constructor(
state(
isFeatureEnabled = component.isEnabled(),
hasFavorites = favorites?.isNotEmpty() == true,
+ hasPanels = serviceInfos.any { it.panelActivity != null },
hasServiceInfos = serviceInfos.isNotEmpty(),
iconResourceId = component.getTileImageId(),
visibility = component.getVisibility(),
@@ -148,13 +149,14 @@ constructor(
private fun state(
isFeatureEnabled: Boolean,
hasFavorites: Boolean,
+ hasPanels: Boolean,
hasServiceInfos: Boolean,
visibility: ControlsComponent.Visibility,
@DrawableRes iconResourceId: Int?,
): KeyguardQuickAffordanceConfig.LockScreenState {
return if (
isFeatureEnabled &&
- hasFavorites &&
+ (hasFavorites || hasPanels) &&
hasServiceInfos &&
iconResourceId != null &&
visibility == ControlsComponent.Visibility.AVAILABLE
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 748c6e8b75b9..57668c795d1c 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
@@ -34,7 +34,6 @@ import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentati
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
-import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract
import com.android.systemui.statusbar.policy.KeyguardStateController
import dagger.Lazy
@@ -62,12 +61,20 @@ constructor(
private val isUsingRepository: Boolean
get() = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)
+ /**
+ * Whether the UI should use the long press gesture to activate quick affordances.
+ *
+ * If `false`, the UI goes back to using single taps.
+ */
+ val useLongPress: Boolean
+ get() = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)
+
/** Returns an observable for the quick affordance at the given position. */
fun quickAffordance(
position: KeyguardQuickAffordancePosition
): Flow<KeyguardQuickAffordanceModel> {
return combine(
- quickAffordanceInternal(position),
+ quickAffordanceAlwaysVisible(position),
keyguardInteractor.isDozing,
keyguardInteractor.isKeyguardShowing,
) { affordance, isDozing, isKeyguardShowing ->
@@ -80,6 +87,19 @@ constructor(
}
/**
+ * Returns an observable for the quick affordance at the given position but always visible,
+ * regardless of lock screen state.
+ *
+ * This is useful for experiences like the lock screen preview mode, where the affordances must
+ * always be visible.
+ */
+ fun quickAffordanceAlwaysVisible(
+ position: KeyguardQuickAffordancePosition,
+ ): Flow<KeyguardQuickAffordanceModel> {
+ return quickAffordanceInternal(position)
+ }
+
+ /**
* Notifies that a quick affordance has been "triggered" (clicked) by the user.
*
* @param configKey The configuration key corresponding to the [KeyguardQuickAffordanceModel] of
@@ -290,15 +310,6 @@ constructor(
}
}
- private fun KeyguardQuickAffordancePosition.toSlotId(): String {
- return when (this) {
- KeyguardQuickAffordancePosition.BOTTOM_START ->
- KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
- KeyguardQuickAffordancePosition.BOTTOM_END ->
- KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END
- }
- }
-
private fun String.encode(slotId: String): String {
return "$slotId$DELIMITER$this"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancePosition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancePosition.kt
index a18b036c5189..2581b595d812 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancePosition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancePosition.kt
@@ -16,8 +16,17 @@
package com.android.systemui.keyguard.shared.quickaffordance
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+
/** Enumerates all possible positions for quick affordances that can appear on the lock-screen. */
enum class KeyguardQuickAffordancePosition {
BOTTOM_START,
- BOTTOM_END,
+ BOTTOM_END;
+
+ fun toSlotId(): String {
+ return when (this) {
+ BOTTOM_START -> KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
+ BOTTOM_END -> KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END
+ }
+ }
}
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 cbe512ff83ba..ae8edfece4cb 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
@@ -16,14 +16,19 @@
package com.android.systemui.keyguard.ui.binder
+import android.annotation.SuppressLint
import android.graphics.drawable.Animatable2
import android.util.Size
import android.util.TypedValue
+import android.view.MotionEvent
import android.view.View
+import android.view.ViewConfiguration
import android.view.ViewGroup
import android.view.ViewPropertyAnimator
import android.widget.ImageView
import android.widget.TextView
+import androidx.core.animation.CycleInterpolator
+import androidx.core.animation.ObjectAnimator
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.Lifecycle
@@ -38,8 +43,10 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.FalsingManager
+import kotlin.math.pow
+import kotlin.math.sqrt
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
@@ -52,6 +59,7 @@ import kotlinx.coroutines.launch
* view-binding, binding each view only once. It is okay and expected for the same instance of the
* view-model to be reused for multiple view/view-binder bindings.
*/
+@OptIn(ExperimentalCoroutinesApi::class)
object KeyguardBottomAreaViewBinder {
private const val EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS = 250L
@@ -84,7 +92,8 @@ object KeyguardBottomAreaViewBinder {
fun bind(
view: ViewGroup,
viewModel: KeyguardBottomAreaViewModel,
- falsingManager: FalsingManager,
+ falsingManager: FalsingManager?,
+ messageDisplayer: (Int) -> Unit,
): Binding {
val indicationArea: View = view.requireViewById(R.id.keyguard_indication_area)
val ambientIndicationArea: View? = view.findViewById(R.id.ambient_indication_container)
@@ -108,6 +117,7 @@ object KeyguardBottomAreaViewBinder {
view = startButton,
viewModel = buttonModel,
falsingManager = falsingManager,
+ messageDisplayer = messageDisplayer,
)
}
}
@@ -118,6 +128,7 @@ object KeyguardBottomAreaViewBinder {
view = endButton,
viewModel = buttonModel,
falsingManager = falsingManager,
+ messageDisplayer = messageDisplayer,
)
}
}
@@ -222,10 +233,12 @@ object KeyguardBottomAreaViewBinder {
}
}
+ @SuppressLint("ClickableViewAccessibility")
private fun updateButton(
view: ImageView,
viewModel: KeyguardQuickAffordanceViewModel,
- falsingManager: FalsingManager,
+ falsingManager: FalsingManager?,
+ messageDisplayer: (Int) -> Unit,
) {
if (!viewModel.isVisible) {
view.isVisible = false
@@ -281,21 +294,126 @@ object KeyguardBottomAreaViewBinder {
},
)
)
+
view.backgroundTintList =
- Utils.getColorAttr(
- view.context,
- if (viewModel.isActivated) {
- com.android.internal.R.attr.colorAccentPrimary
- } else {
- com.android.internal.R.attr.colorSurface
- }
- )
+ if (!viewModel.isSelected) {
+ Utils.getColorAttr(
+ view.context,
+ if (viewModel.isActivated) {
+ com.android.internal.R.attr.colorAccentPrimary
+ } else {
+ com.android.internal.R.attr.colorSurface
+ }
+ )
+ } else {
+ null
+ }
view.isClickable = viewModel.isClickable
if (viewModel.isClickable) {
- view.setOnClickListener(OnClickListener(viewModel, falsingManager))
+ if (viewModel.useLongPress) {
+ view.setOnTouchListener(OnTouchListener(view, viewModel, messageDisplayer))
+ } else {
+ view.setOnClickListener(OnClickListener(viewModel, checkNotNull(falsingManager)))
+ }
} else {
view.setOnClickListener(null)
+ view.setOnTouchListener(null)
+ }
+
+ view.isSelected = viewModel.isSelected
+ }
+
+ private class OnTouchListener(
+ private val view: View,
+ private val viewModel: KeyguardQuickAffordanceViewModel,
+ private val messageDisplayer: (Int) -> Unit,
+ ) : View.OnTouchListener {
+
+ private val longPressDurationMs = ViewConfiguration.getLongPressTimeout().toLong()
+ private var longPressAnimator: ViewPropertyAnimator? = null
+ private var downTimestamp = 0L
+
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onTouch(v: View?, event: MotionEvent?): Boolean {
+ return when (event?.actionMasked) {
+ MotionEvent.ACTION_DOWN ->
+ if (viewModel.configKey != null) {
+ downTimestamp = System.currentTimeMillis()
+ longPressAnimator =
+ view
+ .animate()
+ .scaleX(PRESSED_SCALE)
+ .scaleY(PRESSED_SCALE)
+ .setDuration(longPressDurationMs)
+ .withEndAction {
+ view.setOnClickListener {
+ viewModel.onClicked(
+ KeyguardQuickAffordanceViewModel.OnClickedParameters(
+ configKey = viewModel.configKey,
+ expandable = Expandable.fromView(view),
+ )
+ )
+ }
+ view.performClick()
+ view.setOnClickListener(null)
+ }
+ true
+ } else {
+ false
+ }
+ MotionEvent.ACTION_MOVE -> {
+ if (event.historySize > 0) {
+ val distance =
+ sqrt(
+ (event.y - event.getHistoricalY(0)).pow(2) +
+ (event.x - event.getHistoricalX(0)).pow(2)
+ )
+ if (distance > ViewConfiguration.getTouchSlop()) {
+ cancel()
+ }
+ }
+ true
+ }
+ MotionEvent.ACTION_UP -> {
+ if (System.currentTimeMillis() - downTimestamp < longPressDurationMs) {
+ messageDisplayer.invoke(R.string.keyguard_affordance_press_too_short)
+ val shakeAnimator =
+ ObjectAnimator.ofFloat(
+ view,
+ "translationX",
+ 0f,
+ view.context.resources
+ .getDimensionPixelSize(
+ R.dimen.keyguard_affordance_shake_amplitude
+ )
+ .toFloat(),
+ 0f,
+ )
+ shakeAnimator.duration = 300
+ shakeAnimator.interpolator = CycleInterpolator(5f)
+ shakeAnimator.start()
+ }
+ cancel()
+ true
+ }
+ MotionEvent.ACTION_CANCEL -> {
+ cancel()
+ true
+ }
+ else -> false
+ }
+ }
+
+ private fun cancel() {
+ downTimestamp = 0L
+ longPressAnimator?.cancel()
+ longPressAnimator = null
+ view.animate().scaleX(1f).scaleY(1f)
+ }
+
+ companion object {
+ private const val PRESSED_SCALE = 1.5f
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
new file mode 100644
index 000000000000..a5ae8ba58d45
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.preview
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.hardware.display.DisplayManager
+import android.os.Bundle
+import android.os.IBinder
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.SurfaceControlViewHost
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowManager
+import android.widget.FrameLayout
+import com.android.keyguard.ClockEventController
+import com.android.keyguard.KeyguardClockSwitch
+import com.android.systemui.R
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
+import com.android.systemui.shared.clocks.ClockRegistry
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardQuickAffordancePreviewConstants
+import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.runBlocking
+
+/** Renders the preview of the lock screen. */
+class KeyguardPreviewRenderer
+@AssistedInject
+constructor(
+ @Application private val context: Context,
+ @Main private val mainDispatcher: CoroutineDispatcher,
+ private val bottomAreaViewModel: KeyguardBottomAreaViewModel,
+ displayManager: DisplayManager,
+ private val windowManager: WindowManager,
+ private val clockController: ClockEventController,
+ private val clockRegistry: ClockRegistry,
+ private val broadcastDispatcher: BroadcastDispatcher,
+ @Assisted bundle: Bundle,
+) {
+
+ val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN)
+ private val width: Int = bundle.getInt(KEY_VIEW_WIDTH)
+ private val height: Int = bundle.getInt(KEY_VIEW_HEIGHT)
+
+ private var host: SurfaceControlViewHost
+
+ val surfacePackage: SurfaceControlViewHost.SurfacePackage
+ get() = host.surfacePackage
+
+ private var clockView: View? = null
+
+ private val disposables = mutableSetOf<DisposableHandle>()
+ private var isDestroyed = false
+
+ init {
+ bottomAreaViewModel.enablePreviewMode(
+ initiallySelectedSlotId =
+ bundle.getString(
+ KeyguardQuickAffordancePreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID,
+ ),
+ )
+ runBlocking(mainDispatcher) {
+ host =
+ SurfaceControlViewHost(
+ context,
+ displayManager.getDisplay(bundle.getInt(KEY_DISPLAY_ID)),
+ hostToken,
+ )
+ disposables.add(DisposableHandle { host.release() })
+ }
+ }
+
+ fun render() {
+ runBlocking(mainDispatcher) {
+ val rootView = FrameLayout(context)
+
+ setUpBottomArea(rootView)
+ setUpClock(rootView)
+
+ rootView.measure(
+ View.MeasureSpec.makeMeasureSpec(
+ windowManager.currentWindowMetrics.bounds.width(),
+ View.MeasureSpec.EXACTLY
+ ),
+ View.MeasureSpec.makeMeasureSpec(
+ windowManager.currentWindowMetrics.bounds.height(),
+ View.MeasureSpec.EXACTLY
+ ),
+ )
+ rootView.layout(0, 0, rootView.measuredWidth, rootView.measuredHeight)
+
+ // This aspect scales the view to fit in the surface and centers it
+ val scale: Float =
+ (width / rootView.measuredWidth.toFloat()).coerceAtMost(
+ height / rootView.measuredHeight.toFloat()
+ )
+
+ rootView.scaleX = scale
+ rootView.scaleY = scale
+ rootView.pivotX = 0f
+ rootView.pivotY = 0f
+ rootView.translationX = (width - scale * rootView.width) / 2
+ rootView.translationY = (height - scale * rootView.height) / 2
+
+ host.setView(rootView, rootView.measuredWidth, rootView.measuredHeight)
+ }
+ }
+
+ fun onSlotSelected(slotId: String) {
+ bottomAreaViewModel.onPreviewSlotSelected(slotId = slotId)
+ }
+
+ fun destroy() {
+ isDestroyed = true
+ disposables.forEach { it.dispose() }
+ }
+
+ private fun setUpBottomArea(parentView: ViewGroup) {
+ val bottomAreaView =
+ LayoutInflater.from(context)
+ .inflate(
+ R.layout.keyguard_bottom_area,
+ parentView,
+ false,
+ ) as KeyguardBottomAreaView
+ bottomAreaView.init(
+ viewModel = bottomAreaViewModel,
+ )
+ parentView.addView(
+ bottomAreaView,
+ FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.MATCH_PARENT,
+ FrameLayout.LayoutParams.WRAP_CONTENT,
+ Gravity.BOTTOM,
+ ),
+ )
+ }
+
+ private fun setUpClock(parentView: ViewGroup) {
+ val clockChangeListener = ClockRegistry.ClockChangeListener { onClockChanged(parentView) }
+ clockRegistry.registerClockChangeListener(clockChangeListener)
+ disposables.add(
+ DisposableHandle { clockRegistry.unregisterClockChangeListener(clockChangeListener) }
+ )
+
+ clockController.registerListeners(parentView)
+ disposables.add(DisposableHandle { clockController.unregisterListeners() })
+
+ val receiver =
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ clockController.clock?.events?.onTimeTick()
+ }
+ }
+ broadcastDispatcher.registerReceiver(
+ receiver,
+ IntentFilter().apply {
+ addAction(Intent.ACTION_TIME_TICK)
+ addAction(Intent.ACTION_TIME_CHANGED)
+ },
+ )
+ disposables.add(DisposableHandle { broadcastDispatcher.unregisterReceiver(receiver) })
+
+ onClockChanged(parentView)
+ }
+
+ private fun onClockChanged(parentView: ViewGroup) {
+ clockController.clock = clockRegistry.createCurrentClock()
+ clockController.clock
+ ?.largeClock
+ ?.events
+ ?.onTargetRegionChanged(KeyguardClockSwitch.getLargeClockRegion(parentView))
+ clockView?.let { parentView.removeView(it) }
+ clockView = clockController.clock?.largeClock?.view?.apply { parentView.addView(this) }
+ }
+
+ companion object {
+ private const val KEY_HOST_TOKEN = "host_token"
+ private const val KEY_VIEW_WIDTH = "width"
+ private const val KEY_VIEW_HEIGHT = "height"
+ private const val KEY_DISPLAY_ID = "display_id"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRendererFactory.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRendererFactory.kt
new file mode 100644
index 000000000000..be1d3a18520a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRendererFactory.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.preview
+
+import android.os.Bundle
+import dagger.assisted.AssistedFactory
+
+@AssistedFactory
+interface KeyguardPreviewRendererFactory {
+ fun create(bundle: Bundle): KeyguardPreviewRenderer
+}
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
new file mode 100644
index 000000000000..50722d5c68f8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.preview
+
+import android.os.Bundle
+import android.os.Handler
+import android.os.IBinder
+import android.os.Message
+import android.os.Messenger
+import android.util.ArrayMap
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardQuickAffordancePreviewConstants
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.runBlocking
+
+@SysUISingleton
+class KeyguardRemotePreviewManager
+@Inject
+constructor(
+ private val previewRendererFactory: KeyguardPreviewRendererFactory,
+ @Main private val mainDispatcher: CoroutineDispatcher,
+ @Background private val backgroundHandler: Handler,
+) {
+ private val activePreviews: ArrayMap<IBinder, PreviewLifecycleObserver> =
+ ArrayMap<IBinder, PreviewLifecycleObserver>()
+
+ fun preview(request: Bundle?): Bundle? {
+ if (request == null) {
+ return null
+ }
+
+ var observer: PreviewLifecycleObserver? = null
+ return try {
+ val renderer = previewRendererFactory.create(request)
+
+ // Destroy any previous renderer associated with this token.
+ activePreviews[renderer.hostToken]?.let { destroyObserver(it) }
+ observer = PreviewLifecycleObserver(renderer, mainDispatcher, ::destroyObserver)
+ activePreviews[renderer.hostToken] = observer
+ renderer.render()
+ renderer.hostToken?.linkToDeath(observer, 0)
+ val result = Bundle()
+ result.putParcelable(
+ KEY_PREVIEW_SURFACE_PACKAGE,
+ renderer.surfacePackage,
+ )
+ val messenger =
+ Messenger(
+ Handler(
+ backgroundHandler.looper,
+ observer,
+ )
+ )
+ val msg = Message.obtain()
+ msg.replyTo = messenger
+ result.putParcelable(KEY_PREVIEW_CALLBACK, msg)
+ result
+ } catch (e: Exception) {
+ Log.e(TAG, "Unable to generate preview", e)
+ observer?.let { destroyObserver(it) }
+ null
+ }
+ }
+
+ private fun destroyObserver(observer: PreviewLifecycleObserver) {
+ observer.onDestroy()?.let { hostToken ->
+ if (activePreviews[hostToken] === observer) {
+ activePreviews.remove(hostToken)
+ }
+ }
+ }
+
+ private class PreviewLifecycleObserver(
+ private val renderer: KeyguardPreviewRenderer,
+ private val mainDispatcher: CoroutineDispatcher,
+ private val requestDestruction: (PreviewLifecycleObserver) -> Unit,
+ ) : Handler.Callback, IBinder.DeathRecipient {
+
+ private var isDestroyed = false
+
+ override fun handleMessage(message: Message): Boolean {
+ when (message.what) {
+ KeyguardQuickAffordancePreviewConstants.MESSAGE_ID_SLOT_SELECTED -> {
+ message.data
+ .getString(
+ KeyguardQuickAffordancePreviewConstants.KEY_SLOT_ID,
+ )
+ ?.let { slotId -> renderer.onSlotSelected(slotId = slotId) }
+ }
+ else -> requestDestruction(this)
+ }
+
+ return true
+ }
+
+ override fun binderDied() {
+ requestDestruction(this)
+ }
+
+ fun onDestroy(): IBinder? {
+ if (isDestroyed) {
+ return null
+ }
+
+ isDestroyed = true
+ val hostToken = renderer.hostToken
+ hostToken?.unlinkToDeath(this, 0)
+ runBlocking(mainDispatcher) { renderer.destroy() }
+ return hostToken
+ }
+ }
+
+ companion object {
+ private const val TAG = "KeyguardRemotePreviewManager"
+ @VisibleForTesting const val KEY_PREVIEW_SURFACE_PACKAGE = "surface_package"
+ @VisibleForTesting const val KEY_PREVIEW_CALLBACK = "callback"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
index 227796f43e35..5d85680efcf4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
@@ -24,13 +24,19 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceIn
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
/** View-model for the keyguard bottom area view */
+@OptIn(ExperimentalCoroutinesApi::class)
class KeyguardBottomAreaViewModel
@Inject
constructor(
@@ -40,6 +46,20 @@ constructor(
private val burnInHelperWrapper: BurnInHelperWrapper,
) {
/**
+ * Whether this view-model instance is powering the preview experience that renders exclusively
+ * in the wallpaper picker application. This should _always_ be `false` for the real lock screen
+ * experience.
+ */
+ private val isInPreviewMode = MutableStateFlow(false)
+
+ /**
+ * ID of the slot that's currently selected in the preview that renders exclusively in the
+ * wallpaper picker application. This is ignored for the actual, real lock screen experience.
+ */
+ private val selectedPreviewSlotId =
+ MutableStateFlow(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START)
+
+ /**
* Whether quick affordances are "opaque enough" to be considered visible to and interactive by
* the user. If they are not interactive, user input should not be allowed on them.
*
@@ -66,7 +86,14 @@ constructor(
val isOverlayContainerVisible: Flow<Boolean> =
keyguardInteractor.isDozing.map { !it }.distinctUntilChanged()
/** An observable for the alpha level for the entire bottom area. */
- val alpha: Flow<Float> = bottomAreaInteractor.alpha.distinctUntilChanged()
+ val alpha: Flow<Float> =
+ isInPreviewMode.flatMapLatest { isInPreviewMode ->
+ if (isInPreviewMode) {
+ flowOf(1f)
+ } else {
+ bottomAreaInteractor.alpha.distinctUntilChanged()
+ }
+ }
/** An observable for whether the indication area should be padded. */
val isIndicationAreaPadded: Flow<Boolean> =
combine(startButton, endButton) { startButtonModel, endButtonModel ->
@@ -94,27 +121,61 @@ constructor(
* Returns whether the keyguard bottom area should be constrained to the top of the lock icon
*/
fun shouldConstrainToTopOfLockIcon(): Boolean =
- bottomAreaInteractor.shouldConstrainToTopOfLockIcon()
+ bottomAreaInteractor.shouldConstrainToTopOfLockIcon()
+
+ /**
+ * Puts this view-model in "preview mode", which means it's being used for UI that is rendering
+ * the lock screen preview in wallpaper picker / settings and not the real experience on the
+ * lock screen.
+ *
+ * @param initiallySelectedSlotId The ID of the initial slot to render as the selected one.
+ */
+ fun enablePreviewMode(initiallySelectedSlotId: String?) {
+ isInPreviewMode.value = true
+ onPreviewSlotSelected(
+ initiallySelectedSlotId ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
+ )
+ }
+
+ /**
+ * Notifies that a slot with the given ID has been selected in the preview experience that is
+ * rendering in the wallpaper picker. This is ignored for the real lock screen experience.
+ *
+ * @see enablePreviewMode
+ */
+ fun onPreviewSlotSelected(slotId: String) {
+ selectedPreviewSlotId.value = slotId
+ }
private fun button(
position: KeyguardQuickAffordancePosition
): Flow<KeyguardQuickAffordanceViewModel> {
- return combine(
- quickAffordanceInteractor.quickAffordance(position),
- bottomAreaInteractor.animateDozingTransitions.distinctUntilChanged(),
- areQuickAffordancesFullyOpaque,
- ) { model, animateReveal, isFullyOpaque ->
- model.toViewModel(
- animateReveal = animateReveal,
- isClickable = isFullyOpaque,
- )
- }
- .distinctUntilChanged()
+ return isInPreviewMode.flatMapLatest { isInPreviewMode ->
+ combine(
+ if (isInPreviewMode) {
+ quickAffordanceInteractor.quickAffordanceAlwaysVisible(position = position)
+ } else {
+ quickAffordanceInteractor.quickAffordance(position = position)
+ },
+ bottomAreaInteractor.animateDozingTransitions.distinctUntilChanged(),
+ areQuickAffordancesFullyOpaque,
+ selectedPreviewSlotId,
+ ) { model, animateReveal, isFullyOpaque, selectedPreviewSlotId ->
+ model.toViewModel(
+ animateReveal = !isInPreviewMode && animateReveal,
+ isClickable = isFullyOpaque && !isInPreviewMode,
+ isSelected =
+ (isInPreviewMode && selectedPreviewSlotId == position.toSlotId()),
+ )
+ }
+ .distinctUntilChanged()
+ }
}
private fun KeyguardQuickAffordanceModel.toViewModel(
animateReveal: Boolean,
isClickable: Boolean,
+ isSelected: Boolean,
): KeyguardQuickAffordanceViewModel {
return when (this) {
is KeyguardQuickAffordanceModel.Visible ->
@@ -131,6 +192,8 @@ constructor(
},
isClickable = isClickable,
isActivated = activationState is ActivationState.Active,
+ isSelected = isSelected,
+ useLongPress = quickAffordanceInteractor.useLongPress,
)
is KeyguardQuickAffordanceModel.Hidden -> KeyguardQuickAffordanceViewModel()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
index 44f48f97b62e..cf3a6daa40bb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
@@ -29,6 +29,8 @@ data class KeyguardQuickAffordanceViewModel(
val onClicked: (OnClickedParameters) -> Unit = {},
val isClickable: Boolean = false,
val isActivated: Boolean = false,
+ val isSelected: Boolean = false,
+ val useLongPress: Boolean = false,
) {
data class OnClickedParameters(
val configKey: String,
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
index 3e5d337bff9d..bb833df1ff69 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -30,9 +30,11 @@ import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
import com.android.systemui.media.taptotransfer.MediaTttFlags;
import com.android.systemui.media.taptotransfer.common.MediaTttLogger;
+import com.android.systemui.media.taptotransfer.receiver.ChipReceiverInfo;
import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger;
import com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger;
import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo;
import java.util.Optional;
@@ -95,19 +97,19 @@ public interface MediaModule {
@Provides
@SysUISingleton
@MediaTttSenderLogger
- static MediaTttLogger providesMediaTttSenderLogger(
+ static MediaTttLogger<ChipbarInfo> providesMediaTttSenderLogger(
@MediaTttSenderLogBuffer LogBuffer buffer
) {
- return new MediaTttLogger("Sender", buffer);
+ return new MediaTttLogger<>("Sender", buffer);
}
@Provides
@SysUISingleton
@MediaTttReceiverLogger
- static MediaTttLogger providesMediaTttReceiverLogger(
+ static MediaTttLogger<ChipReceiverInfo> providesMediaTttReceiverLogger(
@MediaTttReceiverLogBuffer LogBuffer buffer
) {
- return new MediaTttLogger("Receiver", buffer);
+ return new MediaTttLogger<>("Receiver", buffer);
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
index b55bedda2dc1..8aef9385fe3e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
@@ -18,17 +18,21 @@ package com.android.systemui.media.taptotransfer.common
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.temporarydisplay.TemporaryViewInfo
import com.android.systemui.temporarydisplay.TemporaryViewLogger
/**
* A logger for media tap-to-transfer events.
*
* @param deviceTypeTag the type of device triggering the logs -- "Sender" or "Receiver".
+ *
+ * TODO(b/245610654): We should de-couple the sender and receiver loggers, since they're vastly
+ * different experiences.
*/
-class MediaTttLogger(
+class MediaTttLogger<T : TemporaryViewInfo>(
deviceTypeTag: String,
buffer: LogBuffer
-) : TemporaryViewLogger(buffer, BASE_TAG + deviceTypeTag) {
+) : TemporaryViewLogger<T>(buffer, BASE_TAG + deviceTypeTag) {
/** Logs a change in the chip state for the given [mediaRouteId]. */
fun logStateChange(stateName: String, mediaRouteId: String, packageName: String?) {
buffer.log(
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
index 009595a6da8b..066c1853818f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
@@ -25,6 +25,7 @@ import com.android.systemui.R
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.TintedIcon
+import com.android.systemui.temporarydisplay.TemporaryViewInfo
/** Utility methods for media tap-to-transfer. */
class MediaTttUtils {
@@ -47,7 +48,7 @@ class MediaTttUtils {
fun getIconInfoFromPackageName(
context: Context,
appPackageName: String?,
- logger: MediaTttLogger
+ logger: MediaTttLogger<out TemporaryViewInfo>
): IconInfo {
if (appPackageName != null) {
val packageManager = context.packageManager
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt
index 40ea1e6e87df..11348adb582c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt
@@ -35,6 +35,14 @@ enum class ChipStateReceiver(
FAR_FROM_SENDER(
StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER,
MediaTttReceiverUiEvents.MEDIA_TTT_RECEIVER_FAR_FROM_SENDER
+ ),
+ TRANSFER_TO_RECEIVER_SUCCEEDED(
+ StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+ MediaTttReceiverUiEvents.MEDIA_TTT_RECEIVER_TRANSFER_TO_RECEIVER_SUCCEEDED,
+ ),
+ TRANSFER_TO_RECEIVER_FAILED(
+ StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_FAILED,
+ MediaTttReceiverUiEvents.MEDIA_TTT_RECEIVER_TRANSFER_TO_RECEIVER_FAILED,
);
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 1c3a53cbf815..7b9d0b4205af 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -45,8 +45,10 @@ import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
import com.android.systemui.temporarydisplay.TemporaryViewInfo
+import com.android.systemui.temporarydisplay.ViewPriority
import com.android.systemui.util.animation.AnimationUtil.Companion.frames
import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.time.SystemClock
import com.android.systemui.util.view.ViewUtil
import com.android.systemui.util.wakelock.WakeLock
import javax.inject.Inject
@@ -62,7 +64,7 @@ import javax.inject.Inject
open class MediaTttChipControllerReceiver @Inject constructor(
private val commandQueue: CommandQueue,
context: Context,
- @MediaTttReceiverLogger logger: MediaTttLogger,
+ @MediaTttReceiverLogger logger: MediaTttLogger<ChipReceiverInfo>,
windowManager: WindowManager,
mainExecutor: DelayableExecutor,
accessibilityManager: AccessibilityManager,
@@ -73,7 +75,8 @@ open class MediaTttChipControllerReceiver @Inject constructor(
private val uiEventLogger: MediaTttReceiverUiEventLogger,
private val viewUtil: ViewUtil,
wakeLockBuilder: WakeLock.Builder,
-) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttLogger>(
+ systemClock: SystemClock,
+) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttLogger<ChipReceiverInfo>>(
context,
logger,
windowManager,
@@ -83,6 +86,7 @@ open class MediaTttChipControllerReceiver @Inject constructor(
powerManager,
R.layout.media_ttt_chip_receiver,
wakeLockBuilder,
+ systemClock,
) {
@SuppressLint("WrongConstant") // We're allowed to use LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
override val windowLayoutParams = commonWindowLayoutParams.apply {
@@ -123,8 +127,8 @@ open class MediaTttChipControllerReceiver @Inject constructor(
}
uiEventLogger.logReceiverStateChange(chipState)
- if (chipState == ChipStateReceiver.FAR_FROM_SENDER) {
- removeView(routeInfo.id, removalReason = ChipStateReceiver.FAR_FROM_SENDER.name)
+ if (chipState != ChipStateReceiver.CLOSE_TO_SENDER) {
+ removeView(routeInfo.id, removalReason = chipState.name)
return
}
if (appIcon == null) {
@@ -290,4 +294,5 @@ data class ChipReceiverInfo(
override val windowTitle: String = MediaTttUtils.WINDOW_TITLE_RECEIVER,
override val wakeReason: String = MediaTttUtils.WAKE_REASON_RECEIVER,
override val id: String,
+ override val priority: ViewPriority = ViewPriority.NORMAL,
) : TemporaryViewInfo()
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverUiEventLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverUiEventLogger.kt
index 39a276329a9b..6e515f27c25e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverUiEventLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverUiEventLogger.kt
@@ -34,7 +34,11 @@ enum class MediaTttReceiverUiEvents(val metricId: Int) : UiEventLogger.UiEventEn
@UiEvent(doc = "See android.app.StatusBarManager.MEDIA_TRANSFER_RECEIVER_* docs")
MEDIA_TTT_RECEIVER_CLOSE_TO_SENDER(982),
@UiEvent(doc = "See android.app.StatusBarManager.MEDIA_TRANSFER_RECEIVER_* docs")
- MEDIA_TTT_RECEIVER_FAR_FROM_SENDER(983);
+ MEDIA_TTT_RECEIVER_FAR_FROM_SENDER(983),
+ @UiEvent(doc = "See android.app.StatusBarManager.MEDIA_TRANSFER_RECEIVER_* docs")
+ MEDIA_TTT_RECEIVER_TRANSFER_TO_RECEIVER_SUCCEEDED(1263),
+ @UiEvent(doc = "See android.app.StatusBarManager.MEDIA_TRANSFER_RECEIVER_* docs")
+ MEDIA_TTT_RECEIVER_TRANSFER_TO_RECEIVER_FAILED(1264);
override fun getId() = metricId
}
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 ec1984d78cf9..9f44d984124f 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
@@ -30,6 +30,7 @@ import com.android.systemui.media.taptotransfer.MediaTttFlags
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.media.taptotransfer.common.MediaTttUtils
import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.temporarydisplay.ViewPriority
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import com.android.systemui.temporarydisplay.chipbar.ChipbarEndItem
import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
@@ -46,7 +47,7 @@ constructor(
private val chipbarCoordinator: ChipbarCoordinator,
private val commandQueue: CommandQueue,
private val context: Context,
- @MediaTttSenderLogger private val logger: MediaTttLogger,
+ @MediaTttSenderLogger private val logger: MediaTttLogger<ChipbarInfo>,
private val mediaTttFlags: MediaTttFlags,
private val uiEventLogger: MediaTttSenderUiEventLogger,
) : CoreStartable {
@@ -146,7 +147,7 @@ constructor(
routeInfo: MediaRoute2Info,
undoCallback: IUndoMediaTransferCallback?,
context: Context,
- logger: MediaTttLogger,
+ logger: MediaTttLogger<ChipbarInfo>,
): ChipbarInfo {
val packageName = routeInfo.clientPackageName
val otherDeviceName = routeInfo.name.toString()
@@ -180,6 +181,7 @@ constructor(
wakeReason = MediaTttUtils.WAKE_REASON_SENDER,
timeoutMs = chipStateSender.timeout,
id = routeInfo.id,
+ priority = ViewPriority.NORMAL,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index f97385bf57e3..6c99b6764909 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -654,8 +654,9 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
}
private void updateMLModelState() {
- boolean newState = mIsEnabled && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.USE_BACK_GESTURE_ML_MODEL, false);
+ boolean newState =
+ mIsGesturalModeEnabled && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.USE_BACK_GESTURE_ML_MODEL, false);
if (newState == mUseMLModel) {
return;
@@ -785,7 +786,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
// ML model
boolean withinMinRange = x < mMLEnableWidth + mLeftInset
|| x >= (mDisplaySize.x - mMLEnableWidth - mRightInset);
- if (!withinMinRange && mUseMLModel
+ if (!withinMinRange && mUseMLModel && !mMLModelIsLoading
&& (results = getBackGesturePredictionsCategory(x, y, app)) != -1) {
withinRange = (results == 1);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index 9743c3e64950..206a62085019 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -358,6 +358,9 @@ public class InternetDialog extends SystemUIDialog implements
if (!isChecked && shouldShowMobileDialog()) {
showTurnOffMobileDialog();
} else if (!shouldShowMobileDialog()) {
+ if (mInternetDialogController.isMobileDataEnabled() == isChecked) {
+ return;
+ }
mInternetDialogController.setMobileDataEnabled(mContext, mDefaultDataSubId,
isChecked, false);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 41084eefb17b..a824d916ec63 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -40,6 +40,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_N
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
+import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
import static com.android.systemui.statusbar.VibratorHelper.TOUCH_VIBRATION_ATTRIBUTES;
import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD;
import static com.android.systemui.util.DumpUtilsKt.asIndenting;
@@ -1327,7 +1328,9 @@ public final class NotificationPanelViewController implements Dumpable {
mKeyguardBottomArea.init(
mKeyguardBottomAreaViewModel,
mFalsingManager,
- mLockIconViewController
+ mLockIconViewController,
+ stringResourceId ->
+ mKeyguardIndicationController.showTransientIndication(stringResourceId)
);
}
@@ -4722,6 +4725,7 @@ public final class NotificationPanelViewController implements Dumpable {
if (!openingWithTouch || !mHasVibratedOnOpen) {
mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
mHasVibratedOnOpen = true;
+ mShadeLog.v("Vibrating on opening, mHasVibratedOnOpen=true");
}
}
}
@@ -5479,6 +5483,15 @@ public final class NotificationPanelViewController implements Dumpable {
mBarState = statusBarState;
mKeyguardShowing = keyguardShowing;
+ boolean fromShadeToKeyguard = statusBarState == KEYGUARD
+ && (oldState == SHADE || oldState == SHADE_LOCKED);
+ if (mSplitShadeEnabled && fromShadeToKeyguard) {
+ // user can go to keyguard from different shade states and closing animation
+ // may not fully run - we always want to make sure we close QS when that happens
+ // as we never need QS open in fresh keyguard state
+ closeQs();
+ }
+
if (oldState == KEYGUARD && (goingToFullShade
|| statusBarState == StatusBarState.SHADE_LOCKED)) {
@@ -5498,27 +5511,12 @@ public final class NotificationPanelViewController implements Dumpable {
mKeyguardStatusBarViewController.animateKeyguardStatusBarIn();
mNotificationStackScrollLayoutController.resetScrollPosition();
- // Only animate header if the header is visible. If not, it will partially
- // animate out
- // the top of QS
- if (!mQsExpanded) {
- // TODO(b/185683835) Nicer clipping when using new spacial model
- if (mSplitShadeEnabled) {
- mQs.animateHeaderSlidingOut();
- }
- }
} else {
// this else branch means we are doing one of:
// - from KEYGUARD to SHADE (but not fully expanded as when swiping from the top)
// - from SHADE to KEYGUARD
// - from SHADE_LOCKED to SHADE
// - getting notified again about the current SHADE or KEYGUARD state
- if (mSplitShadeEnabled && oldState == SHADE && statusBarState == KEYGUARD) {
- // user can go to keyguard from different shade states and closing animation
- // may not fully run - we always want to make sure we close QS when that happens
- // as we never need QS open in fresh keyguard state
- closeQs();
- }
final boolean animatingUnlockedShadeToKeyguard = oldState == SHADE
&& statusBarState == KEYGUARD
&& mScreenOffAnimationController.isKeyguardShowDelayed();
@@ -6122,6 +6120,7 @@ public final class NotificationPanelViewController implements Dumpable {
if (isFullyCollapsed()) {
// If panel is fully collapsed, reset haptic effect before adding movement.
mHasVibratedOnOpen = false;
+ mShadeLog.logHasVibrated(mHasVibratedOnOpen, mExpandedFraction);
}
addMovement(event);
if (!isFullyCollapsed()) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index 0b59af3435ca..5fedbeb556c2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -140,6 +140,15 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) {
})
}
+ fun logHasVibrated(hasVibratedOnOpen: Boolean, fraction: Float) {
+ log(LogLevel.VERBOSE, {
+ bool1 = hasVibratedOnOpen
+ double1 = fraction.toDouble()
+ }, {
+ "hasVibratedOnOpen=$bool1, expansionFraction=$double1"
+ })
+ }
+
fun logQsExpansionChanged(
message: String,
qsExpanded: Boolean,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt
index 0380fff1e2af..1fcf17fe7553 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemory.kt
@@ -17,10 +17,14 @@
package com.android.systemui.statusbar.notification.logging
+import android.app.Notification
+
/** Describes usage of a notification. */
data class NotificationMemoryUsage(
val packageName: String,
+ val uid: Int,
val notificationKey: String,
+ val notification: Notification,
val objectUsage: NotificationObjectUsage,
val viewUsage: List<NotificationViewUsage>
)
@@ -34,7 +38,8 @@ data class NotificationObjectUsage(
val smallIcon: Int,
val largeIcon: Int,
val extras: Int,
- val style: String?,
+ /** Style type, integer from [android.stats.sysui.NotificationEnums] */
+ val style: Int,
val styleIcon: Int,
val bigPicture: Int,
val extender: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryDumper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryDumper.kt
new file mode 100644
index 000000000000..ffd931c1bde2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryDumper.kt
@@ -0,0 +1,173 @@
+/*
+ *
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.logging
+
+import android.stats.sysui.NotificationEnums
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/** Dumps current notification memory use to bug reports for easier debugging. */
+@SysUISingleton
+class NotificationMemoryDumper
+@Inject
+constructor(val dumpManager: DumpManager, val notificationPipeline: NotifPipeline) : Dumpable {
+
+ fun init() {
+ dumpManager.registerNormalDumpable(javaClass.simpleName, this)
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ val memoryUse =
+ NotificationMemoryMeter.notificationMemoryUse(notificationPipeline.allNotifs)
+ .sortedWith(compareBy({ it.packageName }, { it.notificationKey }))
+ dumpNotificationObjects(pw, memoryUse)
+ dumpNotificationViewUsage(pw, memoryUse)
+ }
+
+ /** Renders a table of notification object usage into passed [PrintWriter]. */
+ private fun dumpNotificationObjects(pw: PrintWriter, memoryUse: List<NotificationMemoryUsage>) {
+ pw.println("Notification Object Usage")
+ pw.println("-----------")
+ pw.println(
+ "Package".padEnd(35) +
+ "\t\tSmall\tLarge\t${"Style".padEnd(15)}\t\tStyle\tBig\tExtend.\tExtras\tCustom"
+ )
+ pw.println("".padEnd(35) + "\t\tIcon\tIcon\t${"".padEnd(15)}\t\tIcon\tPicture\t \t \tView")
+ pw.println()
+
+ memoryUse.forEach { use ->
+ pw.println(
+ use.packageName.padEnd(35) +
+ "\t\t" +
+ "${use.objectUsage.smallIcon}\t${use.objectUsage.largeIcon}\t" +
+ (styleEnumToString(use.objectUsage.style).take(15) ?: "").padEnd(15) +
+ "\t\t${use.objectUsage.styleIcon}\t" +
+ "${use.objectUsage.bigPicture}\t${use.objectUsage.extender}\t" +
+ "${use.objectUsage.extras}\t${use.objectUsage.hasCustomView}\t" +
+ use.notificationKey
+ )
+ }
+
+ // Calculate totals for easily glanceable summary.
+ data class Totals(
+ var smallIcon: Int = 0,
+ var largeIcon: Int = 0,
+ var styleIcon: Int = 0,
+ var bigPicture: Int = 0,
+ var extender: Int = 0,
+ var extras: Int = 0,
+ )
+
+ val totals =
+ memoryUse.fold(Totals()) { t, usage ->
+ t.smallIcon += usage.objectUsage.smallIcon
+ t.largeIcon += usage.objectUsage.largeIcon
+ t.styleIcon += usage.objectUsage.styleIcon
+ t.bigPicture += usage.objectUsage.bigPicture
+ t.extender += usage.objectUsage.extender
+ t.extras += usage.objectUsage.extras
+ t
+ }
+
+ pw.println()
+ pw.println("TOTALS")
+ pw.println(
+ "".padEnd(35) +
+ "\t\t" +
+ "${toKb(totals.smallIcon)}\t${toKb(totals.largeIcon)}\t" +
+ "".padEnd(15) +
+ "\t\t${toKb(totals.styleIcon)}\t" +
+ "${toKb(totals.bigPicture)}\t${toKb(totals.extender)}\t" +
+ toKb(totals.extras)
+ )
+ pw.println()
+ }
+
+ /** Renders a table of notification view usage into passed [PrintWriter] */
+ private fun dumpNotificationViewUsage(
+ pw: PrintWriter,
+ memoryUse: List<NotificationMemoryUsage>,
+ ) {
+
+ data class Totals(
+ var smallIcon: Int = 0,
+ var largeIcon: Int = 0,
+ var style: Int = 0,
+ var customViews: Int = 0,
+ var softwareBitmapsPenalty: Int = 0,
+ )
+
+ val totals = Totals()
+ pw.println("Notification View Usage")
+ pw.println("-----------")
+ pw.println("View Type".padEnd(24) + "\tSmall\tLarge\tStyle\tCustom\tSoftware")
+ pw.println("".padEnd(24) + "\tIcon\tIcon\tUse\tView\tBitmaps")
+ pw.println()
+ memoryUse
+ .filter { it.viewUsage.isNotEmpty() }
+ .forEach { use ->
+ pw.println(use.packageName + " " + use.notificationKey)
+ use.viewUsage.forEach { view ->
+ pw.println(
+ " ${view.viewType.toString().padEnd(24)}\t${view.smallIcon}" +
+ "\t${view.largeIcon}\t${view.style}" +
+ "\t${view.customViews}\t${view.softwareBitmapsPenalty}"
+ )
+
+ if (view.viewType == ViewType.TOTAL) {
+ totals.smallIcon += view.smallIcon
+ totals.largeIcon += view.largeIcon
+ totals.style += view.style
+ totals.customViews += view.customViews
+ totals.softwareBitmapsPenalty += view.softwareBitmapsPenalty
+ }
+ }
+ }
+ pw.println()
+ pw.println("TOTALS")
+ pw.println(
+ " ${"".padEnd(24)}\t${toKb(totals.smallIcon)}" +
+ "\t${toKb(totals.largeIcon)}\t${toKb(totals.style)}" +
+ "\t${toKb(totals.customViews)}\t${toKb(totals.softwareBitmapsPenalty)}"
+ )
+ pw.println()
+ }
+
+ private fun styleEnumToString(styleEnum: Int): String =
+ when (styleEnum) {
+ NotificationEnums.STYLE_UNSPECIFIED -> "Unspecified"
+ NotificationEnums.STYLE_NONE -> "None"
+ NotificationEnums.STYLE_BIG_PICTURE -> "BigPicture"
+ NotificationEnums.STYLE_BIG_TEXT -> "BigText"
+ NotificationEnums.STYLE_CALL -> "Call"
+ NotificationEnums.STYLE_DECORATED_CUSTOM_VIEW -> "DCustomView"
+ NotificationEnums.STYLE_INBOX -> "Inbox"
+ NotificationEnums.STYLE_MEDIA -> "Media"
+ NotificationEnums.STYLE_MESSAGING -> "Messaging"
+ NotificationEnums.STYLE_RANKER_GROUP -> "RankerGroup"
+ else -> "Unknown"
+ }
+
+ private fun toKb(bytes: Int): String {
+ return (bytes / 1024).toString() + " KB"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt
new file mode 100644
index 000000000000..ec8501a79fa5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt
@@ -0,0 +1,194 @@
+/*
+ *
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.logging
+
+import android.app.StatsManager
+import android.util.StatsEvent
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.shared.system.SysUiStatsLog
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.util.traceSection
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import kotlin.math.roundToInt
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.runBlocking
+
+/** Periodically logs current state of notification memory consumption. */
+@SysUISingleton
+class NotificationMemoryLogger
+@Inject
+constructor(
+ private val notificationPipeline: NotifPipeline,
+ private val statsManager: StatsManager,
+ @Main private val mainDispatcher: CoroutineDispatcher,
+ @Background private val backgroundExecutor: Executor
+) : StatsManager.StatsPullAtomCallback {
+
+ /**
+ * This class is used to accumulate and aggregate data - the fields mirror values in statd Atom
+ * with ONE IMPORTANT difference - the values are in bytes, not KB!
+ */
+ internal data class NotificationMemoryUseAtomBuilder(val uid: Int, val style: Int) {
+ var count: Int = 0
+ var countWithInflatedViews: Int = 0
+ var smallIconObject: Int = 0
+ var smallIconBitmapCount: Int = 0
+ var largeIconObject: Int = 0
+ var largeIconBitmapCount: Int = 0
+ var bigPictureObject: Int = 0
+ var bigPictureBitmapCount: Int = 0
+ var extras: Int = 0
+ var extenders: Int = 0
+ var smallIconViews: Int = 0
+ var largeIconViews: Int = 0
+ var systemIconViews: Int = 0
+ var styleViews: Int = 0
+ var customViews: Int = 0
+ var softwareBitmaps: Int = 0
+ var seenCount = 0
+ }
+
+ fun init() {
+ statsManager.setPullAtomCallback(
+ SysUiStatsLog.NOTIFICATION_MEMORY_USE,
+ null,
+ backgroundExecutor,
+ this
+ )
+ }
+
+ /** Called by statsd to pull data. */
+ override fun onPullAtom(atomTag: Int, data: MutableList<StatsEvent>): Int =
+ traceSection("NML#onPullAtom") {
+ if (atomTag != SysUiStatsLog.NOTIFICATION_MEMORY_USE) {
+ return StatsManager.PULL_SKIP
+ }
+
+ // Notifications can only be retrieved on the main thread, so switch to that thread.
+ val notifications = getAllNotificationsOnMainThread()
+ val notificationMemoryUse =
+ NotificationMemoryMeter.notificationMemoryUse(notifications)
+ .sortedWith(
+ compareBy(
+ { it.packageName },
+ { it.objectUsage.style },
+ { it.notificationKey }
+ )
+ )
+ val usageData = aggregateMemoryUsageData(notificationMemoryUse)
+ usageData.forEach { (_, use) ->
+ data.add(
+ SysUiStatsLog.buildStatsEvent(
+ SysUiStatsLog.NOTIFICATION_MEMORY_USE,
+ use.uid,
+ use.style,
+ use.count,
+ use.countWithInflatedViews,
+ toKb(use.smallIconObject),
+ use.smallIconBitmapCount,
+ toKb(use.largeIconObject),
+ use.largeIconBitmapCount,
+ toKb(use.bigPictureObject),
+ use.bigPictureBitmapCount,
+ toKb(use.extras),
+ toKb(use.extenders),
+ toKb(use.smallIconViews),
+ toKb(use.largeIconViews),
+ toKb(use.systemIconViews),
+ toKb(use.styleViews),
+ toKb(use.customViews),
+ toKb(use.softwareBitmaps),
+ use.seenCount
+ )
+ )
+ }
+
+ return StatsManager.PULL_SUCCESS
+ }
+
+ private fun getAllNotificationsOnMainThread() =
+ runBlocking(mainDispatcher) {
+ traceSection("NML#getNotifications") { notificationPipeline.allNotifs }
+ }
+
+ /** Aggregates memory usage data by package and style, returning sums. */
+ private fun aggregateMemoryUsageData(
+ notificationMemoryUse: List<NotificationMemoryUsage>
+ ): Map<Pair<String, Int>, NotificationMemoryUseAtomBuilder> {
+ return notificationMemoryUse
+ .groupingBy { Pair(it.packageName, it.objectUsage.style) }
+ .aggregate {
+ _,
+ accumulator: NotificationMemoryUseAtomBuilder?,
+ element: NotificationMemoryUsage,
+ first ->
+ val use =
+ if (first) {
+ NotificationMemoryUseAtomBuilder(element.uid, element.objectUsage.style)
+ } else {
+ accumulator!!
+ }
+
+ use.count++
+ // If the views of the notification weren't inflated, the list of memory usage
+ // parameters will be empty.
+ if (element.viewUsage.isNotEmpty()) {
+ use.countWithInflatedViews++
+ }
+
+ use.smallIconObject += element.objectUsage.smallIcon
+ if (element.objectUsage.smallIcon > 0) {
+ use.smallIconBitmapCount++
+ }
+
+ use.largeIconObject += element.objectUsage.largeIcon
+ if (element.objectUsage.largeIcon > 0) {
+ use.largeIconBitmapCount++
+ }
+
+ use.bigPictureObject += element.objectUsage.bigPicture
+ if (element.objectUsage.bigPicture > 0) {
+ use.bigPictureBitmapCount++
+ }
+
+ use.extras += element.objectUsage.extras
+ use.extenders += element.objectUsage.extender
+
+ // Use totals count which are more accurate when aggregated
+ // in this manner.
+ element.viewUsage
+ .firstOrNull { vu -> vu.viewType == ViewType.TOTAL }
+ ?.let {
+ use.smallIconViews += it.smallIcon
+ use.largeIconViews += it.largeIcon
+ use.systemIconViews += it.systemIcons
+ use.styleViews += it.style
+ use.customViews += it.style
+ use.softwareBitmaps += it.softwareBitmapsPenalty
+ }
+
+ return@aggregate use
+ }
+ }
+
+ /** Rounds the passed value to the nearest KB - e.g. 700B rounds to 1KB. */
+ private fun toKb(value: Int): Int = (value.toFloat() / 1024f).roundToInt()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeter.kt
index 7d39e18ab349..41fb91e3093e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeter.kt
@@ -1,12 +1,20 @@
package com.android.systemui.statusbar.notification.logging
import android.app.Notification
+import android.app.Notification.BigPictureStyle
+import android.app.Notification.BigTextStyle
+import android.app.Notification.CallStyle
+import android.app.Notification.DecoratedCustomViewStyle
+import android.app.Notification.InboxStyle
+import android.app.Notification.MediaStyle
+import android.app.Notification.MessagingStyle
import android.app.Person
import android.graphics.Bitmap
import android.graphics.drawable.Icon
import android.os.Bundle
import android.os.Parcel
import android.os.Parcelable
+import android.stats.sysui.NotificationEnums
import androidx.annotation.WorkerThread
import com.android.systemui.statusbar.notification.NotificationUtils
import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -19,6 +27,7 @@ internal object NotificationMemoryMeter {
private const val TV_EXTENSIONS = "android.tv.EXTENSIONS"
private const val WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"
private const val WEARABLE_EXTENSIONS_BACKGROUND = "background"
+ private const val AUTOGROUP_KEY = "ranker_group"
/** Returns a list of memory use entries for currently shown notifications. */
@WorkerThread
@@ -29,12 +38,15 @@ internal object NotificationMemoryMeter {
.asSequence()
.map { entry ->
val packageName = entry.sbn.packageName
+ val uid = entry.sbn.uid
val notificationObjectUsage =
notificationMemoryUse(entry.sbn.notification, hashSetOf())
val notificationViewUsage = NotificationMemoryViewWalker.getViewUsage(entry.row)
NotificationMemoryUsage(
packageName,
+ uid,
NotificationUtils.logKey(entry.sbn.key),
+ entry.sbn.notification,
notificationObjectUsage,
notificationViewUsage
)
@@ -49,7 +61,9 @@ internal object NotificationMemoryMeter {
): NotificationMemoryUsage {
return NotificationMemoryUsage(
entry.sbn.packageName,
+ entry.sbn.uid,
NotificationUtils.logKey(entry.sbn.key),
+ entry.sbn.notification,
notificationMemoryUse(entry.sbn.notification, seenBitmaps),
NotificationMemoryViewWalker.getViewUsage(entry.row)
)
@@ -116,7 +130,13 @@ internal object NotificationMemoryMeter {
val wearExtenderBackground =
computeParcelableUse(wearExtender, WEARABLE_EXTENSIONS_BACKGROUND, seenBitmaps)
- val style = notification.notificationStyle
+ val style =
+ if (notification.group == AUTOGROUP_KEY) {
+ NotificationEnums.STYLE_RANKER_GROUP
+ } else {
+ styleEnum(notification.notificationStyle)
+ }
+
val hasCustomView = notification.contentView != null || notification.bigContentView != null
val extrasSize = computeBundleSize(extras)
@@ -124,7 +144,7 @@ internal object NotificationMemoryMeter {
smallIcon = smallIconUse,
largeIcon = largeIconUse,
extras = extrasSize,
- style = style?.simpleName,
+ style = style,
styleIcon =
bigPictureIconUse +
peopleUse +
@@ -144,6 +164,25 @@ internal object NotificationMemoryMeter {
}
/**
+ * Returns logging style enum based on current style class.
+ *
+ * @return style value in [NotificationEnums]
+ */
+ private fun styleEnum(style: Class<out Notification.Style>?): Int =
+ when (style?.name) {
+ null -> NotificationEnums.STYLE_NONE
+ BigTextStyle::class.java.name -> NotificationEnums.STYLE_BIG_TEXT
+ BigPictureStyle::class.java.name -> NotificationEnums.STYLE_BIG_PICTURE
+ InboxStyle::class.java.name -> NotificationEnums.STYLE_INBOX
+ MediaStyle::class.java.name -> NotificationEnums.STYLE_MEDIA
+ DecoratedCustomViewStyle::class.java.name ->
+ NotificationEnums.STYLE_DECORATED_CUSTOM_VIEW
+ MessagingStyle::class.java.name -> NotificationEnums.STYLE_MESSAGING
+ CallStyle::class.java.name -> NotificationEnums.STYLE_CALL
+ else -> NotificationEnums.STYLE_UNSPECIFIED
+ }
+
+ /**
* Calculates size of the bundle data (excluding FDs and other shared objects like ashmem
* bitmaps). Can be slow.
*/
@@ -176,7 +215,7 @@ internal object NotificationMemoryMeter {
*
* @return memory usage in bytes or 0 if the icon is Uri/Resource based
*/
- private fun computeIconUse(icon: Icon?, seenBitmaps: HashSet<Int>) =
+ private fun computeIconUse(icon: Icon?, seenBitmaps: HashSet<Int>): Int =
when (icon?.type) {
Icon.TYPE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps)
Icon.TYPE_ADAPTIVE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
index c09cc4306ced..f38c1e557b25 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
@@ -18,11 +18,10 @@
package com.android.systemui.statusbar.notification.logging
import android.util.Log
-import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.notification.collection.NotifPipeline
-import java.io.PrintWriter
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import dagger.Lazy
import javax.inject.Inject
/** This class monitors and logs current Notification memory use. */
@@ -30,9 +29,10 @@ import javax.inject.Inject
class NotificationMemoryMonitor
@Inject
constructor(
- val notificationPipeline: NotifPipeline,
- val dumpManager: DumpManager,
-) : Dumpable {
+ private val featureFlags: FeatureFlags,
+ private val notificationMemoryDumper: NotificationMemoryDumper,
+ private val notificationMemoryLogger: Lazy<NotificationMemoryLogger>,
+) {
companion object {
private const val TAG = "NotificationMemory"
@@ -40,127 +40,10 @@ constructor(
fun init() {
Log.d(TAG, "NotificationMemoryMonitor initialized.")
- dumpManager.registerDumpable(javaClass.simpleName, this)
- }
-
- override fun dump(pw: PrintWriter, args: Array<out String>) {
- val memoryUse =
- NotificationMemoryMeter.notificationMemoryUse(notificationPipeline.allNotifs)
- .sortedWith(compareBy({ it.packageName }, { it.notificationKey }))
- dumpNotificationObjects(pw, memoryUse)
- dumpNotificationViewUsage(pw, memoryUse)
- }
-
- /** Renders a table of notification object usage into passed [PrintWriter]. */
- private fun dumpNotificationObjects(pw: PrintWriter, memoryUse: List<NotificationMemoryUsage>) {
- pw.println("Notification Object Usage")
- pw.println("-----------")
- pw.println(
- "Package".padEnd(35) +
- "\t\tSmall\tLarge\t${"Style".padEnd(15)}\t\tStyle\tBig\tExtend.\tExtras\tCustom"
- )
- pw.println("".padEnd(35) + "\t\tIcon\tIcon\t${"".padEnd(15)}\t\tIcon\tPicture\t \t \tView")
- pw.println()
-
- memoryUse.forEach { use ->
- pw.println(
- use.packageName.padEnd(35) +
- "\t\t" +
- "${use.objectUsage.smallIcon}\t${use.objectUsage.largeIcon}\t" +
- (use.objectUsage.style?.take(15) ?: "").padEnd(15) +
- "\t\t${use.objectUsage.styleIcon}\t" +
- "${use.objectUsage.bigPicture}\t${use.objectUsage.extender}\t" +
- "${use.objectUsage.extras}\t${use.objectUsage.hasCustomView}\t" +
- use.notificationKey
- )
+ notificationMemoryDumper.init()
+ if (featureFlags.isEnabled(Flags.NOTIFICATION_MEMORY_LOGGING_ENABLED)) {
+ Log.d(TAG, "Notification memory logging enabled.")
+ notificationMemoryLogger.get().init()
}
-
- // Calculate totals for easily glanceable summary.
- data class Totals(
- var smallIcon: Int = 0,
- var largeIcon: Int = 0,
- var styleIcon: Int = 0,
- var bigPicture: Int = 0,
- var extender: Int = 0,
- var extras: Int = 0,
- )
-
- val totals =
- memoryUse.fold(Totals()) { t, usage ->
- t.smallIcon += usage.objectUsage.smallIcon
- t.largeIcon += usage.objectUsage.largeIcon
- t.styleIcon += usage.objectUsage.styleIcon
- t.bigPicture += usage.objectUsage.bigPicture
- t.extender += usage.objectUsage.extender
- t.extras += usage.objectUsage.extras
- t
- }
-
- pw.println()
- pw.println("TOTALS")
- pw.println(
- "".padEnd(35) +
- "\t\t" +
- "${toKb(totals.smallIcon)}\t${toKb(totals.largeIcon)}\t" +
- "".padEnd(15) +
- "\t\t${toKb(totals.styleIcon)}\t" +
- "${toKb(totals.bigPicture)}\t${toKb(totals.extender)}\t" +
- toKb(totals.extras)
- )
- pw.println()
- }
-
- /** Renders a table of notification view usage into passed [PrintWriter] */
- private fun dumpNotificationViewUsage(
- pw: PrintWriter,
- memoryUse: List<NotificationMemoryUsage>,
- ) {
-
- data class Totals(
- var smallIcon: Int = 0,
- var largeIcon: Int = 0,
- var style: Int = 0,
- var customViews: Int = 0,
- var softwareBitmapsPenalty: Int = 0,
- )
-
- val totals = Totals()
- pw.println("Notification View Usage")
- pw.println("-----------")
- pw.println("View Type".padEnd(24) + "\tSmall\tLarge\tStyle\tCustom\tSoftware")
- pw.println("".padEnd(24) + "\tIcon\tIcon\tUse\tView\tBitmaps")
- pw.println()
- memoryUse
- .filter { it.viewUsage.isNotEmpty() }
- .forEach { use ->
- pw.println(use.packageName + " " + use.notificationKey)
- use.viewUsage.forEach { view ->
- pw.println(
- " ${view.viewType.toString().padEnd(24)}\t${view.smallIcon}" +
- "\t${view.largeIcon}\t${view.style}" +
- "\t${view.customViews}\t${view.softwareBitmapsPenalty}"
- )
-
- if (view.viewType == ViewType.TOTAL) {
- totals.smallIcon += view.smallIcon
- totals.largeIcon += view.largeIcon
- totals.style += view.style
- totals.customViews += view.customViews
- totals.softwareBitmapsPenalty += view.softwareBitmapsPenalty
- }
- }
- }
- pw.println()
- pw.println("TOTALS")
- pw.println(
- " ${"".padEnd(24)}\t${toKb(totals.smallIcon)}" +
- "\t${toKb(totals.largeIcon)}\t${toKb(totals.style)}" +
- "\t${toKb(totals.customViews)}\t${toKb(totals.softwareBitmapsPenalty)}"
- )
- pw.println()
- }
-
- private fun toKb(bytes: Int): String {
- return (bytes / 1024).toString() + " KB"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt
index a0bee1502f51..2d042118b3d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt
@@ -50,7 +50,11 @@ internal object NotificationMemoryViewWalker {
/**
* Returns memory usage of public and private views contained in passed
- * [ExpandableNotificationRow]
+ * [ExpandableNotificationRow]. Each entry will correspond to one of the [ViewType] values with
+ * [ViewType.TOTAL] totalling all memory use. If a type of view is missing, the corresponding
+ * entry will not appear in resulting list.
+ *
+ * This will return an empty list if the ExpandableNotificationRow has no views inflated.
*/
fun getViewUsage(row: ExpandableNotificationRow?): List<NotificationViewUsage> {
if (row == null) {
@@ -58,42 +62,72 @@ internal object NotificationMemoryViewWalker {
}
// The ordering here is significant since it determines deduplication of seen drawables.
- return listOf(
- getViewUsage(ViewType.PRIVATE_EXPANDED_VIEW, row.privateLayout?.expandedChild),
- getViewUsage(ViewType.PRIVATE_CONTRACTED_VIEW, row.privateLayout?.contractedChild),
- getViewUsage(ViewType.PRIVATE_HEADS_UP_VIEW, row.privateLayout?.headsUpChild),
- getViewUsage(ViewType.PUBLIC_VIEW, row.publicLayout),
- getTotalUsage(row)
- )
+ val perViewUsages =
+ listOf(
+ getViewUsage(ViewType.PRIVATE_EXPANDED_VIEW, row.privateLayout?.expandedChild),
+ getViewUsage(
+ ViewType.PRIVATE_CONTRACTED_VIEW,
+ row.privateLayout?.contractedChild
+ ),
+ getViewUsage(ViewType.PRIVATE_HEADS_UP_VIEW, row.privateLayout?.headsUpChild),
+ getViewUsage(
+ ViewType.PUBLIC_VIEW,
+ row.publicLayout?.expandedChild,
+ row.publicLayout?.contractedChild,
+ row.publicLayout?.headsUpChild
+ ),
+ )
+ .filterNotNull()
+
+ return if (perViewUsages.isNotEmpty()) {
+ // Attach summed totals field only if there was any view actually measured.
+ // This reduces bug report noise and makes checks for collapsed views easier.
+ val totals = getTotalUsage(row)
+ if (totals == null) {
+ perViewUsages
+ } else {
+ perViewUsages + totals
+ }
+ } else {
+ listOf()
+ }
}
/**
* Calculate total usage of all views - we need to do a separate traversal to make sure we don't
* double count fields.
*/
- private fun getTotalUsage(row: ExpandableNotificationRow): NotificationViewUsage {
- val totalUsage = UsageBuilder()
+ private fun getTotalUsage(row: ExpandableNotificationRow): NotificationViewUsage? {
val seenObjects = hashSetOf<Int>()
-
- row.publicLayout?.let { computeViewHierarchyUse(it, totalUsage, seenObjects) }
- row.privateLayout?.let { child ->
- for (view in listOf(child.expandedChild, child.contractedChild, child.headsUpChild)) {
- (view as? ViewGroup)?.let { v ->
- computeViewHierarchyUse(v, totalUsage, seenObjects)
- }
- }
- }
- return totalUsage.build(ViewType.TOTAL)
+ return getViewUsage(
+ ViewType.TOTAL,
+ row.privateLayout?.expandedChild,
+ row.privateLayout?.contractedChild,
+ row.privateLayout?.headsUpChild,
+ row.publicLayout?.expandedChild,
+ row.publicLayout?.contractedChild,
+ row.publicLayout?.headsUpChild,
+ seenObjects = seenObjects
+ )
}
private fun getViewUsage(
type: ViewType,
- rootView: View?,
+ vararg rootViews: View?,
seenObjects: HashSet<Int> = hashSetOf()
- ): NotificationViewUsage {
- val usageBuilder = UsageBuilder()
- (rootView as? ViewGroup)?.let { computeViewHierarchyUse(it, usageBuilder, seenObjects) }
- return usageBuilder.build(type)
+ ): NotificationViewUsage? {
+ val usageBuilder = lazy { UsageBuilder() }
+ rootViews.forEach { rootView ->
+ (rootView as? ViewGroup)?.let { rootViewGroup ->
+ computeViewHierarchyUse(rootViewGroup, usageBuilder.value, seenObjects)
+ }
+ }
+
+ return if (usageBuilder.isInitialized()) {
+ usageBuilder.value.build(type)
+ } else {
+ null
+ }
}
private fun computeViewHierarchyUse(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index aff7b4c6c515..b6cf9482f00d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -871,8 +871,7 @@ public class StackScrollAlgorithm {
}
for (int i = childCount - 1; i >= 0; i--) {
- childrenOnTop = updateChildZValue(i, childrenOnTop,
- algorithmState, ambientState, i == topHunIndex);
+ updateChildZValue(i, algorithmState, ambientState, i == topHunIndex);
}
}
@@ -882,15 +881,11 @@ public class StackScrollAlgorithm {
*
* @param isTopHun Whether the child is a top HUN. A top HUN means a HUN that shows on the
* vertically top of screen. Top HUNs should have drop shadows
- * @param childrenOnTop It is greater than 0 when there's an existing HUN that is elevated
- * @return childrenOnTop The decimal part represents the fraction of the elevated HUN's height
- * that overlaps with QQS Panel. The integer part represents the count of
- * previous HUNs whose Z positions are greater than 0.
*/
- protected float updateChildZValue(int i, float childrenOnTop,
- StackScrollAlgorithmState algorithmState,
- AmbientState ambientState,
- boolean isTopHun) {
+ protected void updateChildZValue(int i,
+ StackScrollAlgorithmState algorithmState,
+ AmbientState ambientState,
+ boolean isTopHun) {
ExpandableView child = algorithmState.visibleChildren.get(i);
ExpandableViewState childViewState = child.getViewState();
float baseZ = ambientState.getBaseZHeight();
@@ -904,22 +899,16 @@ public class StackScrollAlgorithm {
// Handles HUN shadow when Shade is opened, and AmbientState.mScrollY > 0
// Calculate the HUN's z-value based on its overlapping fraction with QQS Panel.
// When scrolling down shade to make HUN back to in-position in Notification Panel,
- // The over-lapping fraction goes to 0, and shadows hides gradually.
- if (childrenOnTop != 0.0f) {
- // To elevate the later HUN over previous HUN
- childrenOnTop++;
- } else {
- float overlap = ambientState.getTopPadding()
- + ambientState.getStackTranslation() - childViewState.getYTranslation();
- // To prevent over-shadow during HUN entry
- childrenOnTop += Math.min(
- 1.0f,
- overlap / childViewState.height
- );
- MathUtils.saturate(childrenOnTop);
+ // the overlapFraction goes to 0, and the pinned HUN's shadows hides gradually.
+ float overlap = ambientState.getTopPadding()
+ + ambientState.getStackTranslation() - childViewState.getYTranslation();
+
+ if (childViewState.height > 0) { // To avoid 0/0 problems
+ // To prevent over-shadow
+ float overlapFraction = MathUtils.saturate(overlap / childViewState.height);
+ childViewState.setZTranslation(baseZ
+ + overlapFraction * mPinnedZTranslationExtra);
}
- childViewState.setZTranslation(baseZ
- + childrenOnTop * mPinnedZTranslationExtra);
} else if (isTopHun) {
// In case this is a new view that has never been measured before, we don't want to
// elevate if we are currently expanded more than the notification
@@ -947,15 +936,14 @@ public class StackScrollAlgorithm {
}
// Handles HUN shadow when shade is closed.
- // While HUN is showing and Shade is closed: headerVisibleAmount stays 0, shadow stays.
+ // While shade is closed, and during HUN's entry: headerVisibleAmount stays 0, shadow stays.
+ // While shade is closed, and HUN is showing: headerVisibleAmount stays 0, shadow stays.
// During HUN-to-Shade (eg. dragging down HUN to open Shade): headerVisibleAmount goes
// gradually from 0 to 1, shadow hides gradually.
// Header visibility is a deprecated concept, we are using headerVisibleAmount only because
// this value nicely goes from 0 to 1 during the HUN-to-Shade process.
-
childViewState.setZTranslation(childViewState.getZTranslation()
+ (1.0f - child.getHeaderVisibleAmount()) * mPinnedZTranslationExtra);
- return childrenOnTop;
}
public void setIsExpanded(boolean isExpanded) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 6b72e9696f83..936589c77d40 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -371,6 +371,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
if (!mKeyguardStateController.isShowing()) {
final Intent cameraIntent = CameraIntents.getInsecureCameraIntent(mContext);
+ cameraIntent.putExtra(CameraIntents.EXTRA_LAUNCH_SOURCE, source);
mCentralSurfaces.startActivityDismissingKeyguard(cameraIntent,
false /* onlyProvisioned */, true /* dismissShade */,
true /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */, 0,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index b394535ca011..a409ad8815a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone;
+import static android.app.StatusBarManager.DISABLE_HOME;
import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
import static android.app.StatusBarManager.WindowVisibleState;
@@ -69,6 +70,7 @@ import android.graphics.Point;
import android.hardware.devicestate.DeviceStateManager;
import android.metrics.LogMaker;
import android.net.Uri;
+import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -1030,8 +1032,21 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
// set the initial view visibility
int disabledFlags1 = result.mDisabledFlags1;
int disabledFlags2 = result.mDisabledFlags2;
- mInitController.addPostInitTask(
- () -> setUpDisableFlags(disabledFlags1, disabledFlags2));
+ mInitController.addPostInitTask(() -> {
+ setUpDisableFlags(disabledFlags1, disabledFlags2);
+ try {
+ // NOTE(b/262059863): Force-update the disable flags after applying the flags
+ // returned from registerStatusBar(). The result's disabled flags may be stale
+ // if StatusBarManager's disabled flags are updated between registering the bar and
+ // this handling this post-init task. We force an update in this case, and use a new
+ // token to not conflict with any other disabled flags already requested by SysUI
+ Binder token = new Binder();
+ mBarService.disable(DISABLE_HOME, token, mContext.getPackageName());
+ mBarService.disable(0, token, mContext.getPackageName());
+ } catch (RemoteException ex) {
+ ex.rethrowFromSystemServer();
+ }
+ });
mFalsingManager.addFalsingBeliefListener(mFalsingBeliefListener);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
index 78b28d203629..2ce116394236 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
@@ -23,7 +23,7 @@ import android.view.ViewGroup
import android.view.ViewPropertyAnimator
import android.view.WindowInsets
import android.widget.FrameLayout
-import com.android.keyguard.KeyguardUpdateMonitor
+import androidx.annotation.StringRes
import com.android.keyguard.LockIconViewController
import com.android.systemui.R
import com.android.systemui.keyguard.ui.binder.KeyguardBottomAreaViewBinder
@@ -51,21 +51,29 @@ constructor(
defStyleRes,
) {
+ interface MessageDisplayer {
+ fun display(@StringRes stringResourceId: Int)
+ }
+
private var ambientIndicationArea: View? = null
private lateinit var binding: KeyguardBottomAreaViewBinder.Binding
- private lateinit var lockIconViewController: LockIconViewController
+ private var lockIconViewController: LockIconViewController? = null
/** Initializes the view. */
fun init(
viewModel: KeyguardBottomAreaViewModel,
- falsingManager: FalsingManager,
- lockIconViewController: LockIconViewController,
+ falsingManager: FalsingManager? = null,
+ lockIconViewController: LockIconViewController? = null,
+ messageDisplayer: MessageDisplayer? = null,
) {
- binding = bind(
+ binding =
+ bind(
this,
viewModel,
falsingManager,
- )
+ ) {
+ messageDisplayer?.display(it)
+ }
this.lockIconViewController = lockIconViewController
}
@@ -129,21 +137,21 @@ constructor(
findViewById<View>(R.id.ambient_indication_container)?.let {
val (ambientLeft, ambientTop) = it.locationOnScreen
if (binding.shouldConstrainToTopOfLockIcon()) {
- //make top of ambient indication view the bottom of the lock icon
+ // make top of ambient indication view the bottom of the lock icon
it.layout(
- ambientLeft,
- lockIconViewController.bottom.toInt(),
- right - ambientLeft,
- ambientTop + it.measuredHeight
+ ambientLeft,
+ lockIconViewController?.bottom?.toInt() ?: 0,
+ right - ambientLeft,
+ ambientTop + it.measuredHeight
)
} else {
- //make bottom of ambient indication view the top of the lock icon
- val lockLocationTop = lockIconViewController.top
+ // make bottom of ambient indication view the top of the lock icon
+ val lockLocationTop = lockIconViewController?.top ?: 0
it.layout(
- ambientLeft,
- lockLocationTop.toInt() - it.measuredHeight,
- right - ambientLeft,
- lockLocationTop.toInt()
+ ambientLeft,
+ lockLocationTop.toInt() - it.measuredHeight,
+ right - ambientLeft,
+ lockLocationTop.toInt()
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/OWNERS b/packages/SystemUI/src/com/android/systemui/stylus/OWNERS
new file mode 100644
index 000000000000..7ccb316dbca5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/stylus/OWNERS
@@ -0,0 +1,8 @@
+# Bug component: 1254381
+azappone@google.com
+achalke@google.com
+juliacr@google.com
+madym@google.com
+mgalhardo@google.com
+petrcermak@google.com
+vanjan@google.com \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
index ea4020861a09..db7315f311ac 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -34,6 +34,7 @@ import com.android.systemui.CoreStartable
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.time.SystemClock
import com.android.systemui.util.wakelock.WakeLock
/**
@@ -44,8 +45,24 @@ import com.android.systemui.util.wakelock.WakeLock
*
* The generic type T is expected to contain all the information necessary for the subclasses to
* display the view in a certain state, since they receive <T> in [updateView].
+ *
+ * Some information about display ordering:
+ *
+ * [ViewPriority] defines different priorities for the incoming views. The incoming view will be
+ * displayed so long as its priority is equal to or greater than the currently displayed view.
+ * (Concretely, this means that a [ViewPriority.NORMAL] won't be displayed if a
+ * [ViewPriority.CRITICAL] is currently displayed. But otherwise, the incoming view will get
+ * displayed and kick out the old view).
+ *
+ * Once the currently displayed view times out, we *may* display a previously requested view if it
+ * still has enough time left before its own timeout. The same priority ordering applies.
+ *
+ * Note: [TemporaryViewInfo.id] is the identifier that we use to determine if a call to
+ * [displayView] will just update the current view with new information, or display a completely new
+ * view. This means that you *cannot* change the [TemporaryViewInfo.priority] or
+ * [TemporaryViewInfo.windowTitle] while using the same ID.
*/
-abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : TemporaryViewLogger>(
+abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : TemporaryViewLogger<T>>(
internal val context: Context,
internal val logger: U,
internal val windowManager: WindowManager,
@@ -55,6 +72,7 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
private val powerManager: PowerManager,
@LayoutRes private val viewLayoutRes: Int,
private val wakeLockBuilder: WakeLock.Builder,
+ private val systemClock: SystemClock,
) : CoreStartable {
/**
* Window layout params that will be used as a starting point for the [windowLayoutParams] of
@@ -78,27 +96,18 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
*/
internal abstract val windowLayoutParams: WindowManager.LayoutParams
- /** A container for all the display-related objects. Null if the view is not being displayed. */
- private var displayInfo: DisplayInfo? = null
-
- /** A [Runnable] that, when run, will cancel the pending timeout of the view. */
- private var cancelViewTimeout: Runnable? = null
-
/**
- * A wakelock that is acquired when view is displayed and screen off,
- * then released when view is removed.
+ * A list of the currently active views, ordered from highest priority in the beginning to
+ * lowest priority at the end.
+ *
+ * Whenever the current view disappears, the next-priority view will be displayed if it's still
+ * valid.
*/
- private var wakeLock: WakeLock? = null
-
- /** A string that keeps track of wakelock reason once it is acquired till it gets released */
- private var wakeReasonAcquired: String? = null
+ internal val activeViews: MutableList<DisplayInfo> = mutableListOf()
- /**
- * A stack of pairs of device id and temporary view info. This is used when there may be
- * multiple devices in range, and we want to always display the chip for the most recently
- * active device.
- */
- internal val activeViews: ArrayDeque<Pair<String, T>> = ArrayDeque()
+ private fun getCurrentDisplayInfo(): DisplayInfo? {
+ return activeViews.getOrNull(0)
+ }
/**
* Displays the view with the provided [newInfo].
@@ -107,94 +116,139 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
* display the correct information in the view.
* @param onViewTimeout a runnable that runs after the view timeout.
*/
+ @Synchronized
fun displayView(newInfo: T, onViewTimeout: Runnable? = null) {
- val currentDisplayInfo = displayInfo
-
- // Update our list of active devices by removing it if necessary, then adding back at the
- // front of the list
- val id = newInfo.id
- val position = findAndRemoveFromActiveViewsList(id)
- activeViews.addFirst(Pair(id, newInfo))
-
- if (currentDisplayInfo != null &&
- currentDisplayInfo.info.windowTitle == newInfo.windowTitle) {
- // We're already displaying information in the correctly-titled window, so we just need
- // to update the view.
- currentDisplayInfo.info = newInfo
- updateView(currentDisplayInfo.info, currentDisplayInfo.view)
- } else {
- if (currentDisplayInfo != null) {
- // We're already displaying information but that information is under a different
- // window title. So, we need to remove the old window with the old title and add a
- // new window with the new title.
- removeView(
- id,
- removalReason = "New info has new window title: ${newInfo.windowTitle}"
- )
- }
-
- // At this point, we're guaranteed to no longer be displaying a view.
- // So, set up all our callbacks and inflate the view.
- configurationController.addCallback(displayScaleListener)
-
- wakeLock = if (!powerManager.isScreenOn) {
- // If the screen is off, fully wake it so the user can see the view.
- wakeLockBuilder
- .setTag(newInfo.windowTitle)
- .setLevelsAndFlags(
- PowerManager.FULL_WAKE_LOCK or
- PowerManager.ACQUIRE_CAUSES_WAKEUP
- )
- .build()
- } else {
- // Per b/239426653, we want the view to show over the dream state.
- // If the screen is on, using screen bright level will leave screen on the dream
- // state but ensure the screen will not go off before wake lock is released.
- wakeLockBuilder
- .setTag(newInfo.windowTitle)
- .setLevelsAndFlags(PowerManager.SCREEN_BRIGHT_WAKE_LOCK)
- .build()
- }
- wakeLock?.acquire(newInfo.wakeReason)
- wakeReasonAcquired = newInfo.wakeReason
- logger.logViewAddition(id, newInfo.windowTitle)
- inflateAndUpdateView(newInfo)
- }
-
- // Cancel and re-set the view timeout each time we get a new state.
val timeout = accessibilityManager.getRecommendedTimeoutMillis(
newInfo.timeoutMs,
// Not all views have controls so FLAG_CONTENT_CONTROLS might be superfluous, but
// include it just to be safe.
FLAG_CONTENT_ICONS or FLAG_CONTENT_TEXT or FLAG_CONTENT_CONTROLS
- )
+ )
+ val timeExpirationMillis = systemClock.currentTimeMillis() + timeout
+
+ val currentDisplayInfo = getCurrentDisplayInfo()
+
+ // We're current displaying a chipbar with the same ID, we just need to update its info
+ if (currentDisplayInfo != null && currentDisplayInfo.info.id == newInfo.id) {
+ val view = checkNotNull(currentDisplayInfo.view) {
+ "First item in activeViews list must have a valid view"
+ }
+ logger.logViewUpdate(newInfo)
+ currentDisplayInfo.info = newInfo
+ currentDisplayInfo.timeExpirationMillis = timeExpirationMillis
+ updateTimeout(currentDisplayInfo, timeout, onViewTimeout)
+ updateView(newInfo, view)
+ return
+ }
+
+ val newDisplayInfo = DisplayInfo(
+ info = newInfo,
+ onViewTimeout = onViewTimeout,
+ timeExpirationMillis = timeExpirationMillis,
+ // Null values will be updated to non-null if/when this view actually gets displayed
+ view = null,
+ wakeLock = null,
+ cancelViewTimeout = null,
+ )
+
+ // We're not displaying anything, so just render this new info
+ if (currentDisplayInfo == null) {
+ addCallbacks()
+ activeViews.add(newDisplayInfo)
+ showNewView(newDisplayInfo, timeout)
+ return
+ }
+
+ // The currently displayed info takes higher priority than the new one.
+ // So, just store the new one in case the current one disappears.
+ if (currentDisplayInfo.info.priority > newInfo.priority) {
+ logger.logViewAdditionDelayed(newInfo)
+ // Remove any old information for this id (if it exists) and re-add it to the list in
+ // the right priority spot
+ removeFromActivesIfNeeded(newInfo.id)
+ var insertIndex = 0
+ while (insertIndex < activeViews.size &&
+ activeViews[insertIndex].info.priority > newInfo.priority) {
+ insertIndex++
+ }
+ activeViews.add(insertIndex, newDisplayInfo)
+ return
+ }
+
+ // Else: The newInfo should be displayed and the currentInfo should be hidden
+ hideView(currentDisplayInfo)
+ // Remove any old information for this id (if it exists) and put this info at the beginning
+ removeFromActivesIfNeeded(newDisplayInfo.info.id)
+ activeViews.add(0, newDisplayInfo)
+ showNewView(newDisplayInfo, timeout)
+ }
+
+ private fun showNewView(newDisplayInfo: DisplayInfo, timeout: Int) {
+ logger.logViewAddition(newDisplayInfo.info)
+ createAndAcquireWakeLock(newDisplayInfo)
+ updateTimeout(newDisplayInfo, timeout, newDisplayInfo.onViewTimeout)
+ inflateAndUpdateView(newDisplayInfo)
+ }
- // Only cancel timeout of the most recent view displayed, as it will be reset.
- if (position == 0) {
- cancelViewTimeout?.run()
+ private fun createAndAcquireWakeLock(displayInfo: DisplayInfo) {
+ // TODO(b/262009503): Migrate off of isScrenOn, since it's deprecated.
+ val newWakeLock = if (!powerManager.isScreenOn) {
+ // If the screen is off, fully wake it so the user can see the view.
+ wakeLockBuilder
+ .setTag(displayInfo.info.windowTitle)
+ .setLevelsAndFlags(
+ PowerManager.FULL_WAKE_LOCK or
+ PowerManager.ACQUIRE_CAUSES_WAKEUP
+ )
+ .build()
+ } else {
+ // Per b/239426653, we want the view to show over the dream state.
+ // If the screen is on, using screen bright level will leave screen on the dream
+ // state but ensure the screen will not go off before wake lock is released.
+ wakeLockBuilder
+ .setTag(displayInfo.info.windowTitle)
+ .setLevelsAndFlags(PowerManager.SCREEN_BRIGHT_WAKE_LOCK)
+ .build()
}
- cancelViewTimeout = mainExecutor.executeDelayed(
+ displayInfo.wakeLock = newWakeLock
+ newWakeLock.acquire(displayInfo.info.wakeReason)
+ }
+
+ /**
+ * Creates a runnable that will remove [displayInfo] in [timeout] ms from now.
+ *
+ * @param onViewTimeout an optional runnable that will be run if the view times out.
+ * @return a runnable that, when run, will *cancel* the view's timeout.
+ */
+ private fun updateTimeout(displayInfo: DisplayInfo, timeout: Int, onViewTimeout: Runnable?) {
+ val cancelViewTimeout = mainExecutor.executeDelayed(
{
- removeView(id, REMOVAL_REASON_TIMEOUT)
+ removeView(displayInfo.info.id, REMOVAL_REASON_TIMEOUT)
onViewTimeout?.run()
},
timeout.toLong()
)
+
+ displayInfo.onViewTimeout = onViewTimeout
+ // Cancel old view timeout and re-set it.
+ displayInfo.cancelViewTimeout?.run()
+ displayInfo.cancelViewTimeout = cancelViewTimeout
}
- /** Inflates a new view, updates it with [newInfo], and adds the view to the window. */
- private fun inflateAndUpdateView(newInfo: T) {
+ /** Inflates a new view, updates it with [DisplayInfo.info], and adds the view to the window. */
+ private fun inflateAndUpdateView(displayInfo: DisplayInfo) {
+ val newInfo = displayInfo.info
val newView = LayoutInflater
.from(context)
.inflate(viewLayoutRes, null) as ViewGroup
- val newViewController = TouchableRegionViewController(newView, this::getTouchableRegion)
- newViewController.init()
+ displayInfo.view = newView
// We don't need to hold on to the view controller since we never set anything additional
// on it -- it will be automatically cleaned up when the view is detached.
- val newDisplayInfo = DisplayInfo(newView, newInfo)
- displayInfo = newDisplayInfo
- updateView(newDisplayInfo.info, newDisplayInfo.view)
+ val newViewController = TouchableRegionViewController(newView, this::getTouchableRegion)
+ newViewController.init()
+
+ updateView(newInfo, newView)
val paramsWithTitle = WindowManager.LayoutParams().also {
it.copyFrom(windowLayoutParams)
@@ -206,11 +260,15 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
}
/** Removes then re-inflates the view. */
+ @Synchronized
private fun reinflateView() {
- val currentViewInfo = displayInfo ?: return
+ val currentDisplayInfo = getCurrentDisplayInfo() ?: return
- windowManager.removeView(currentViewInfo.view)
- inflateAndUpdateView(currentViewInfo.info)
+ val view = checkNotNull(currentDisplayInfo.view) {
+ "First item in activeViews list must have a valid view"
+ }
+ windowManager.removeView(view)
+ inflateAndUpdateView(currentDisplayInfo)
}
private val displayScaleListener = object : ConfigurationController.ConfigurationListener {
@@ -219,68 +277,109 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
}
}
+ private fun addCallbacks() {
+ configurationController.addCallback(displayScaleListener)
+ }
+
+ private fun removeCallbacks() {
+ configurationController.removeCallback(displayScaleListener)
+ }
+
/**
- * Hides the view given its [id].
+ * Completely removes the view for the given [id], both visually and from our internal store.
*
* @param id the id of the device responsible of displaying the temp view.
* @param removalReason a short string describing why the view was removed (timeout, state
* change, etc.)
*/
+ @Synchronized
fun removeView(id: String, removalReason: String) {
- val currentDisplayInfo = displayInfo ?: return
+ logger.logViewRemoval(id, removalReason)
- val removalPosition = findAndRemoveFromActiveViewsList(id)
- if (removalPosition == null) {
- logger.logViewRemovalIgnored(id, "view not found in the list")
+ val displayInfo = activeViews.firstOrNull { it.info.id == id }
+ if (displayInfo == null) {
+ logger.logViewRemovalIgnored(id, "View not found in list")
return
}
- if (removalPosition != 0) {
- logger.logViewRemovalIgnored(id, "most recent view is being displayed.")
+
+ val currentlyDisplayedView = activeViews[0]
+ // Remove immediately (instead as part of the animation end runnable) so that if a new view
+ // event comes in while this view is animating out, we still display the new view
+ // appropriately.
+ activeViews.remove(displayInfo)
+
+ // No need to time the view out since it's already gone
+ displayInfo.cancelViewTimeout?.run()
+
+ if (displayInfo.view == null) {
+ logger.logViewRemovalIgnored(id, "No view to remove")
return
}
- logger.logViewRemoval(id, removalReason)
- val newViewToDisplay = if (activeViews.isEmpty()) {
- null
- } else {
- activeViews[0].second
+ if (currentlyDisplayedView.info.id != id) {
+ logger.logViewRemovalIgnored(id, "View isn't the currently displayed view")
+ return
}
- val currentView = currentDisplayInfo.view
- animateViewOut(currentView) {
- windowManager.removeView(currentView)
- wakeLock?.release(wakeReasonAcquired)
- }
+ removeViewFromWindow(displayInfo)
- configurationController.removeCallback(displayScaleListener)
- // Re-set to null immediately (instead as part of the animation end runnable) so
- // that if a new view event comes in while this view is animating out, we still display
- // the new view appropriately.
- displayInfo = null
- // No need to time the view out since it's already gone
- cancelViewTimeout?.run()
+ // Prune anything that's already timed out before determining if we should re-display a
+ // different chipbar.
+ removeTimedOutViews()
+ val newViewToDisplay = getCurrentDisplayInfo()
if (newViewToDisplay != null) {
- mainExecutor.executeDelayed({ displayView(newViewToDisplay)}, DISPLAY_VIEW_DELAY)
+ val timeout = newViewToDisplay.timeExpirationMillis - systemClock.currentTimeMillis()
+ // TODO(b/258019006): We may want to have a delay before showing the new view so
+ // that the UI translation looks a bit smoother. But, we expect this to happen
+ // rarely so it may not be worth the extra complexity.
+ showNewView(newViewToDisplay, timeout.toInt())
+ } else {
+ removeCallbacks()
}
}
/**
- * Finds and removes the active view with the given [id] from the stack, or null if there is no
- * active view with that ID
- *
- * @param id that temporary view belonged to.
- *
- * @return index of the view in the stack , otherwise null.
+ * Hides the view from the window, but keeps [displayInfo] around in [activeViews] in case it
+ * should be re-displayed later.
*/
- private fun findAndRemoveFromActiveViewsList(id: String): Int? {
- for (i in 0 until activeViews.size) {
- if (activeViews[i].first == id) {
- activeViews.removeAt(i)
- return i
- }
+ private fun hideView(displayInfo: DisplayInfo) {
+ logger.logViewHidden(displayInfo.info)
+ removeViewFromWindow(displayInfo)
+ }
+
+ private fun removeViewFromWindow(displayInfo: DisplayInfo) {
+ val view = displayInfo.view
+ if (view == null) {
+ logger.logViewRemovalIgnored(displayInfo.info.id, "View is null")
+ return
+ }
+ displayInfo.view = null // Need other places??
+ animateViewOut(view) {
+ windowManager.removeView(view)
+ displayInfo.wakeLock?.release(displayInfo.info.wakeReason)
+ }
+ }
+
+ @Synchronized
+ private fun removeTimedOutViews() {
+ val invalidViews = activeViews
+ .filter { it.timeExpirationMillis <
+ systemClock.currentTimeMillis() + MIN_REQUIRED_TIME_FOR_REDISPLAY }
+
+ invalidViews.forEach {
+ activeViews.remove(it)
+ logger.logViewExpiration(it.info)
+ }
+ }
+
+ @Synchronized
+ private fun removeFromActivesIfNeeded(id: String) {
+ val toRemove = activeViews.find { it.info.id == id }
+ toRemove?.let {
+ it.cancelViewTimeout?.run()
+ activeViews.remove(it)
}
- return null
}
/**
@@ -311,17 +410,47 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
}
/** A container for all the display-related state objects. */
- private inner class DisplayInfo(
- /** The view currently being displayed. */
- val view: ViewGroup,
-
- /** The info currently being displayed. */
+ inner class DisplayInfo(
+ /**
+ * The view currently being displayed.
+ *
+ * Null if this info isn't currently being displayed.
+ */
+ var view: ViewGroup?,
+
+ /** The info that should be displayed if/when this is the highest priority view. */
var info: T,
+
+ /**
+ * The system time at which this display info should expire and never be displayed again.
+ */
+ var timeExpirationMillis: Long,
+
+ /**
+ * The wake lock currently held by this view. Must be released when the view disappears.
+ *
+ * Null if this info isn't currently being displayed.
+ */
+ var wakeLock: WakeLock?,
+
+ /**
+ * See [displayView].
+ */
+ var onViewTimeout: Runnable?,
+
+ /**
+ * A runnable that, when run, will cancel this view's timeout.
+ *
+ * Null if this info isn't currently being displayed.
+ */
+ var cancelViewTimeout: Runnable?,
)
+
+ // TODO(b/258019006): Add a dump method that dumps the currently active views.
}
private const val REMOVAL_REASON_TIMEOUT = "TIMEOUT"
-const val DISPLAY_VIEW_DELAY = 50L
+private const val MIN_REQUIRED_TIME_FOR_REDISPLAY = 1000
private data class IconInfo(
val iconName: String,
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt
index df8396051dda..5596cf68b4bc 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt
@@ -42,6 +42,20 @@ abstract class TemporaryViewInfo {
* The id of the temporary view.
*/
abstract val id: String
+
+ /** The priority for this view. */
+ abstract val priority: ViewPriority
}
const val DEFAULT_TIMEOUT_MILLIS = 10000
+
+/**
+ * The priority of the view being displayed.
+ *
+ * Must be ordered from lowest priority to highest priority. (CRITICAL is currently the highest
+ * priority.)
+ */
+enum class ViewPriority {
+ NORMAL,
+ CRITICAL,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
index 133a384e7e17..ec6965a83b5a 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
@@ -20,20 +20,79 @@ import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel
/** A logger for temporary view changes -- see [TemporaryViewDisplayController]. */
-open class TemporaryViewLogger(
+open class TemporaryViewLogger<T : TemporaryViewInfo>(
internal val buffer: LogBuffer,
internal val tag: String,
) {
- /** Logs that we added the view with the given [id] in a window titled [windowTitle]. */
- fun logViewAddition(id: String, windowTitle: String) {
+ fun logViewExpiration(info: T) {
buffer.log(
tag,
LogLevel.DEBUG,
{
- str1 = windowTitle
- str2 = id
+ str1 = info.id
+ str2 = info.windowTitle
+ str3 = info.priority.name
+ },
+ { "View timeout has already expired; removing. id=$str1 window=$str2 priority=$str3" }
+ )
+ }
+
+ fun logViewUpdate(info: T) {
+ buffer.log(
+ tag,
+ LogLevel.DEBUG,
+ {
+ str1 = info.id
+ str2 = info.windowTitle
+ str3 = info.priority.name
},
- { "View added. window=$str1 id=$str2" }
+ { "Existing view updated with new data. id=$str1 window=$str2 priority=$str3" }
+ )
+ }
+
+ fun logViewAdditionDelayed(info: T) {
+ buffer.log(
+ tag,
+ LogLevel.DEBUG,
+ {
+ str1 = info.id
+ str2 = info.windowTitle
+ str3 = info.priority.name
+ },
+ {
+ "New view can't be displayed because higher priority view is currently " +
+ "displayed. New view id=$str1 window=$str2 priority=$str3"
+ }
+ )
+ }
+
+ /** Logs that we added the view with the given information. */
+ fun logViewAddition(info: T) {
+ buffer.log(
+ tag,
+ LogLevel.DEBUG,
+ {
+ str1 = info.id
+ str2 = info.windowTitle
+ str3 = info.priority.name
+ },
+ { "View added. id=$str1 window=$str2 priority=$str3" }
+ )
+ }
+
+ fun logViewHidden(info: T) {
+ buffer.log(
+ tag,
+ LogLevel.DEBUG,
+ {
+ str1 = info.id
+ str2 = info.windowTitle
+ str3 = info.priority.name
+ },
+ {
+ "View hidden in favor of newer view. " +
+ "Hidden view id=$str1 window=$str2 priority=$str3"
+ }
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index 4d91e35856dc..14ba63a2738f 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -43,6 +43,7 @@ import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.time.SystemClock
import com.android.systemui.util.view.ViewUtil
import com.android.systemui.util.wakelock.WakeLock
import javax.inject.Inject
@@ -77,6 +78,7 @@ open class ChipbarCoordinator @Inject constructor(
private val viewUtil: ViewUtil,
private val vibratorHelper: VibratorHelper,
wakeLockBuilder: WakeLock.Builder,
+ systemClock: SystemClock,
) : TemporaryViewDisplayController<ChipbarInfo, ChipbarLogger>(
context,
logger,
@@ -87,6 +89,7 @@ open class ChipbarCoordinator @Inject constructor(
powerManager,
R.layout.chipbar,
wakeLockBuilder,
+ systemClock,
) {
private lateinit var parent: ChipbarRootView
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
index a3eef8032b3b..dd4bd26e3bcd 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
@@ -22,6 +22,7 @@ import androidx.annotation.AttrRes
import com.android.systemui.common.shared.model.Text
import com.android.systemui.common.shared.model.TintedIcon
import com.android.systemui.temporarydisplay.TemporaryViewInfo
+import com.android.systemui.temporarydisplay.ViewPriority
/**
* A container for all the state needed to display a chipbar via [ChipbarCoordinator].
@@ -42,6 +43,7 @@ data class ChipbarInfo(
override val wakeReason: String,
override val timeoutMs: Int,
override val id: String,
+ override val priority: ViewPriority,
) : TemporaryViewInfo() {
companion object {
@AttrRes const val DEFAULT_ICON_TINT_ATTR = android.R.attr.textColorPrimary
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
index e477cd68673a..fcfbe0aeedf6 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
@@ -29,7 +29,7 @@ class ChipbarLogger
@Inject
constructor(
@ChipbarLog buffer: LogBuffer,
-) : TemporaryViewLogger(buffer, "ChipbarLog") {
+) : TemporaryViewLogger<ChipbarInfo>(buffer, "ChipbarLog") {
/**
* Logs that the chipbar was updated to display in a window named [windowTitle], with [text] and
* [endItemDesc].
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index 8bbaf3dff1e5..10595439200a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -19,6 +19,7 @@ package com.android.keyguard;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -87,6 +88,7 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase {
when(mAbsKeyInputView.isAttachedToWindow()).thenReturn(true);
when(mAbsKeyInputView.requireViewById(R.id.bouncer_message_area))
.thenReturn(mKeyguardMessageArea);
+ when(mAbsKeyInputView.getResources()).thenReturn(getContext().getResources());
mKeyguardAbsKeyInputViewController = new KeyguardAbsKeyInputViewController(mAbsKeyInputView,
mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
mKeyguardMessageAreaControllerFactory, mLatencyTracker, mFalsingCollector,
@@ -99,6 +101,11 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase {
public void onResume(int reason) {
super.onResume(reason);
}
+
+ @Override
+ protected int getInitialMessageResId() {
+ return 0;
+ }
};
mKeyguardAbsKeyInputViewController.init();
reset(mKeyguardMessageAreaController); // Clear out implicit call to init.
@@ -125,4 +132,22 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase {
verifyZeroInteractions(mKeyguardSecurityCallback);
verifyZeroInteractions(mKeyguardMessageAreaController);
}
+
+ @Test
+ public void onPromptReasonNone_doesNotSetMessage() {
+ mKeyguardAbsKeyInputViewController.showPromptReason(0);
+ verify(mKeyguardMessageAreaController, never()).setMessage(
+ getContext().getResources().getString(R.string.kg_prompt_reason_restart_password),
+ false);
+ }
+
+ @Test
+ public void onPromptReason_setsMessage() {
+ when(mAbsKeyInputView.getPromptReasonStringRes(1)).thenReturn(
+ R.string.kg_prompt_reason_restart_password);
+ mKeyguardAbsKeyInputViewController.showPromptReason(1);
+ verify(mKeyguardMessageAreaController).setMessage(
+ getContext().getResources().getString(R.string.kg_prompt_reason_restart_password),
+ false);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index d20be56d6c6b..d91279399341 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -30,64 +30,54 @@ import com.android.systemui.util.concurrency.DelayableExecutor
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class KeyguardPasswordViewControllerTest : SysuiTestCase() {
- @Mock
- private lateinit var keyguardPasswordView: KeyguardPasswordView
- @Mock
- private lateinit var passwordEntry: EditText
- @Mock
- lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
- @Mock
- lateinit var securityMode: KeyguardSecurityModel.SecurityMode
- @Mock
- lateinit var lockPatternUtils: LockPatternUtils
- @Mock
- lateinit var keyguardSecurityCallback: KeyguardSecurityCallback
- @Mock
- lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory
- @Mock
- lateinit var latencyTracker: LatencyTracker
- @Mock
- lateinit var inputMethodManager: InputMethodManager
- @Mock
- lateinit var emergencyButtonController: EmergencyButtonController
- @Mock
- lateinit var mainExecutor: DelayableExecutor
- @Mock
- lateinit var falsingCollector: FalsingCollector
- @Mock
- lateinit var keyguardViewController: KeyguardViewController
- @Mock
- private lateinit var mKeyguardMessageArea: BouncerKeyguardMessageArea
- @Mock
- private lateinit var mKeyguardMessageAreaController:
- KeyguardMessageAreaController<BouncerKeyguardMessageArea>
+ @Mock private lateinit var keyguardPasswordView: KeyguardPasswordView
+ @Mock private lateinit var passwordEntry: EditText
+ @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock lateinit var securityMode: KeyguardSecurityModel.SecurityMode
+ @Mock lateinit var lockPatternUtils: LockPatternUtils
+ @Mock lateinit var keyguardSecurityCallback: KeyguardSecurityCallback
+ @Mock lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory
+ @Mock lateinit var latencyTracker: LatencyTracker
+ @Mock lateinit var inputMethodManager: InputMethodManager
+ @Mock lateinit var emergencyButtonController: EmergencyButtonController
+ @Mock lateinit var mainExecutor: DelayableExecutor
+ @Mock lateinit var falsingCollector: FalsingCollector
+ @Mock lateinit var keyguardViewController: KeyguardViewController
+ @Mock private lateinit var mKeyguardMessageArea: BouncerKeyguardMessageArea
+ @Mock
+ private lateinit var mKeyguardMessageAreaController:
+ KeyguardMessageAreaController<BouncerKeyguardMessageArea>
- private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController
+ private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- Mockito.`when`(
- keyguardPasswordView
- .requireViewById<BouncerKeyguardMessageArea>(R.id.bouncer_message_area)
- ).thenReturn(mKeyguardMessageArea)
- Mockito.`when`(messageAreaControllerFactory.create(mKeyguardMessageArea))
- .thenReturn(mKeyguardMessageAreaController)
- Mockito.`when`(keyguardPasswordView.passwordTextViewId).thenReturn(R.id.passwordEntry)
- Mockito.`when`(keyguardPasswordView.findViewById<EditText>(R.id.passwordEntry)
- ).thenReturn(passwordEntry)
- keyguardPasswordViewController = KeyguardPasswordViewController(
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ Mockito.`when`(
+ keyguardPasswordView.requireViewById<BouncerKeyguardMessageArea>(
+ R.id.bouncer_message_area))
+ .thenReturn(mKeyguardMessageArea)
+ Mockito.`when`(messageAreaControllerFactory.create(mKeyguardMessageArea))
+ .thenReturn(mKeyguardMessageAreaController)
+ Mockito.`when`(keyguardPasswordView.passwordTextViewId).thenReturn(R.id.passwordEntry)
+ Mockito.`when`(keyguardPasswordView.findViewById<EditText>(R.id.passwordEntry))
+ .thenReturn(passwordEntry)
+ `when`(keyguardPasswordView.resources).thenReturn(context.resources)
+ keyguardPasswordViewController =
+ KeyguardPasswordViewController(
keyguardPasswordView,
keyguardUpdateMonitor,
securityMode,
@@ -100,51 +90,48 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() {
mainExecutor,
mContext.resources,
falsingCollector,
- keyguardViewController
- )
- }
+ keyguardViewController)
+ }
- @Test
- fun testFocusWhenBouncerIsShown() {
- Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(true)
- Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
- keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
- keyguardPasswordView.post {
- verify(keyguardPasswordView).requestFocus()
- verify(keyguardPasswordView).showKeyboard()
- }
+ @Test
+ fun testFocusWhenBouncerIsShown() {
+ Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(true)
+ Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
+ keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
+ keyguardPasswordView.post {
+ verify(keyguardPasswordView).requestFocus()
+ verify(keyguardPasswordView).showKeyboard()
}
+ }
- @Test
- fun testDoNotFocusWhenBouncerIsHidden() {
- Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(false)
- Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
- keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
- verify(keyguardPasswordView, never()).requestFocus()
- }
+ @Test
+ fun testDoNotFocusWhenBouncerIsHidden() {
+ Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(false)
+ Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
+ keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
+ verify(keyguardPasswordView, never()).requestFocus()
+ }
- @Test
- fun testHideKeyboardWhenOnPause() {
- keyguardPasswordViewController.onPause()
- keyguardPasswordView.post {
- verify(keyguardPasswordView).clearFocus()
- verify(keyguardPasswordView).hideKeyboard()
- }
+ @Test
+ fun testHideKeyboardWhenOnPause() {
+ keyguardPasswordViewController.onPause()
+ keyguardPasswordView.post {
+ verify(keyguardPasswordView).clearFocus()
+ verify(keyguardPasswordView).hideKeyboard()
}
+ }
- @Test
- fun startAppearAnimation() {
- keyguardPasswordViewController.startAppearAnimation()
- verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_password)
- }
+ @Test
+ fun startAppearAnimation() {
+ keyguardPasswordViewController.startAppearAnimation()
+ verify(mKeyguardMessageAreaController)
+ .setMessage(context.resources.getString(R.string.keyguard_enter_your_password), false)
+ }
- @Test
- fun startAppearAnimation_withExistingMessage() {
- `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
- keyguardPasswordViewController.startAppearAnimation()
- verify(
- mKeyguardMessageAreaController,
- never()
- ).setMessage(R.string.keyguard_enter_your_password)
- }
+ @Test
+ fun startAppearAnimation_withExistingMessage() {
+ `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
+ keyguardPasswordViewController.startAppearAnimation()
+ verify(mKeyguardMessageAreaController, never()).setMessage(anyString(), anyBoolean())
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index b3d1c8f909d8..85dbdb8330a3 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -30,97 +30,93 @@ import com.android.systemui.statusbar.policy.DevicePostureController
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
-import org.mockito.Mockito.never
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class KeyguardPatternViewControllerTest : SysuiTestCase() {
- @Mock
- private lateinit var mKeyguardPatternView: KeyguardPatternView
+ @Mock private lateinit var mKeyguardPatternView: KeyguardPatternView
- @Mock
- private lateinit var mKeyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock private lateinit var mKeyguardUpdateMonitor: KeyguardUpdateMonitor
- @Mock
- private lateinit var mSecurityMode: KeyguardSecurityModel.SecurityMode
+ @Mock private lateinit var mSecurityMode: KeyguardSecurityModel.SecurityMode
- @Mock
- private lateinit var mLockPatternUtils: LockPatternUtils
+ @Mock private lateinit var mLockPatternUtils: LockPatternUtils
- @Mock
- private lateinit var mKeyguardSecurityCallback: KeyguardSecurityCallback
+ @Mock private lateinit var mKeyguardSecurityCallback: KeyguardSecurityCallback
- @Mock
- private lateinit var mLatencyTracker: LatencyTracker
- private var mFalsingCollector: FalsingCollector = FalsingCollectorFake()
+ @Mock private lateinit var mLatencyTracker: LatencyTracker
+ private var mFalsingCollector: FalsingCollector = FalsingCollectorFake()
- @Mock
- private lateinit var mEmergencyButtonController: EmergencyButtonController
+ @Mock private lateinit var mEmergencyButtonController: EmergencyButtonController
- @Mock
- private lateinit
- var mKeyguardMessageAreaControllerFactory: KeyguardMessageAreaController.Factory
+ @Mock
+ private lateinit var mKeyguardMessageAreaControllerFactory: KeyguardMessageAreaController.Factory
- @Mock
- private lateinit var mKeyguardMessageArea: BouncerKeyguardMessageArea
+ @Mock private lateinit var mKeyguardMessageArea: BouncerKeyguardMessageArea
- @Mock
- private lateinit var mKeyguardMessageAreaController:
- KeyguardMessageAreaController<BouncerKeyguardMessageArea>
+ @Mock
+ private lateinit var mKeyguardMessageAreaController:
+ KeyguardMessageAreaController<BouncerKeyguardMessageArea>
- @Mock
- private lateinit var mLockPatternView: LockPatternView
+ @Mock private lateinit var mLockPatternView: LockPatternView
- @Mock
- private lateinit var mPostureController: DevicePostureController
+ @Mock private lateinit var mPostureController: DevicePostureController
- private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController
+ private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- `when`(mKeyguardPatternView.isAttachedToWindow).thenReturn(true)
- `when`(mKeyguardPatternView
- .requireViewById<BouncerKeyguardMessageArea>(R.id.bouncer_message_area))
- .thenReturn(mKeyguardMessageArea)
- `when`(mKeyguardPatternView.findViewById<LockPatternView>(R.id.lockPatternView))
- .thenReturn(mLockPatternView)
- `when`(mKeyguardMessageAreaControllerFactory.create(mKeyguardMessageArea))
- .thenReturn(mKeyguardMessageAreaController)
- mKeyguardPatternViewController = KeyguardPatternViewController(
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ `when`(mKeyguardPatternView.isAttachedToWindow).thenReturn(true)
+ `when`(
+ mKeyguardPatternView.requireViewById<BouncerKeyguardMessageArea>(
+ R.id.bouncer_message_area))
+ .thenReturn(mKeyguardMessageArea)
+ `when`(mKeyguardPatternView.findViewById<LockPatternView>(R.id.lockPatternView))
+ .thenReturn(mLockPatternView)
+ `when`(mKeyguardMessageAreaControllerFactory.create(mKeyguardMessageArea))
+ .thenReturn(mKeyguardMessageAreaController)
+ `when`(mKeyguardPatternView.resources).thenReturn(context.resources)
+ mKeyguardPatternViewController =
+ KeyguardPatternViewController(
mKeyguardPatternView,
- mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
- mLatencyTracker, mFalsingCollector, mEmergencyButtonController,
- mKeyguardMessageAreaControllerFactory, mPostureController
- )
- }
-
- @Test
- fun onPause_resetsText() {
- mKeyguardPatternViewController.init()
- mKeyguardPatternViewController.onPause()
- verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern)
- }
-
-
- @Test
- fun startAppearAnimation() {
- mKeyguardPatternViewController.startAppearAnimation()
- verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern)
- }
-
- @Test
- fun startAppearAnimation_withExistingMessage() {
- `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
- mKeyguardPatternViewController.startAppearAnimation()
- verify(
- mKeyguardMessageAreaController,
- never()
- ).setMessage(R.string.keyguard_enter_your_password)
- }
+ mKeyguardUpdateMonitor,
+ mSecurityMode,
+ mLockPatternUtils,
+ mKeyguardSecurityCallback,
+ mLatencyTracker,
+ mFalsingCollector,
+ mEmergencyButtonController,
+ mKeyguardMessageAreaControllerFactory,
+ mPostureController)
+ }
+
+ @Test
+ fun onPause_resetsText() {
+ mKeyguardPatternViewController.init()
+ mKeyguardPatternViewController.onPause()
+ verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern)
+ }
+
+ @Test
+ fun startAppearAnimation() {
+ mKeyguardPatternViewController.startAppearAnimation()
+ verify(mKeyguardMessageAreaController)
+ .setMessage(context.resources.getString(R.string.keyguard_enter_your_pattern), false)
+ }
+
+ @Test
+ fun startAppearAnimation_withExistingMessage() {
+ `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
+ mKeyguardPatternViewController.startAppearAnimation()
+ verify(mKeyguardMessageAreaController, never()).setMessage(anyString(), anyBoolean())
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index ce1101f389c0..b7421001b57e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -16,6 +16,8 @@
package com.android.keyguard;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -113,4 +115,9 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase {
mKeyguardPinViewController.onResume(KeyguardSecurityView.SCREEN_ON);
verify(mPasswordEntry).requestFocus();
}
+
+ @Test
+ public void testGetInitialMessageResId() {
+ assertThat(mKeyguardPinViewController.getInitialMessageResId()).isNotEqualTo(0);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 8bcfe6f2b6f5..cdb7bbb9f823 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -31,10 +31,13 @@ import com.android.systemui.statusbar.policy.DevicePostureController
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.any
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@@ -79,6 +82,7 @@ class KeyguardPinViewControllerTest : SysuiTestCase() {
keyguardMessageAreaControllerFactory.create(any(KeyguardMessageArea::class.java))
)
.thenReturn(keyguardMessageAreaController)
+ `when`(keyguardPinView.resources).thenReturn(context.resources)
pinViewController =
KeyguardPinViewController(
keyguardPinView,
@@ -98,14 +102,14 @@ class KeyguardPinViewControllerTest : SysuiTestCase() {
@Test
fun startAppearAnimation() {
pinViewController.startAppearAnimation()
- verify(keyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pin)
+ verify(keyguardMessageAreaController)
+ .setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false)
}
@Test
fun startAppearAnimation_withExistingMessage() {
Mockito.`when`(keyguardMessageAreaController.message).thenReturn("Unlock to continue.")
pinViewController.startAppearAnimation()
- verify(keyguardMessageAreaController, Mockito.never())
- .setMessage(R.string.keyguard_enter_your_password)
+ verify(keyguardMessageAreaController, Mockito.never()).setMessage(anyString(), anyBoolean())
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index bdd29aa93b2c..0f4cf4119731 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -2083,6 +2083,96 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
}
+ @Test
+ public void fingerprintFailure_requestActiveUnlock_dismissKeyguard()
+ throws RemoteException {
+ // GIVEN shouldTriggerActiveUnlock
+ bouncerFullyVisible();
+ when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+
+ // GIVEN active unlock triggers on biometric failures
+ when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+ ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL))
+ .thenReturn(true);
+
+ // WHEN fingerprint fails
+ mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback.onAuthenticationFailed();
+
+ // ALWAYS request unlock with a keyguard dismissal
+ verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+ eq(true));
+ }
+
+ @Test
+ public void faceNonBypassFailure_requestActiveUnlock_doesNotDismissKeyguard()
+ throws RemoteException {
+ // GIVEN shouldTriggerActiveUnlock
+ when(mAuthController.isUdfpsFingerDown()).thenReturn(false);
+ keyguardIsVisible();
+ keyguardNotGoingAway();
+ statusBarShadeIsNotLocked();
+ when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+
+ // GIVEN active unlock triggers on biometric failures
+ when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+ ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL))
+ .thenReturn(true);
+
+ // WHEN face fails & bypass is not allowed
+ lockscreenBypassIsNotAllowed();
+ mKeyguardUpdateMonitor.mFaceAuthenticationCallback.onAuthenticationFailed();
+
+ // THEN request unlock with NO keyguard dismissal
+ verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+ eq(false));
+ }
+
+ @Test
+ public void faceBypassFailure_requestActiveUnlock_dismissKeyguard()
+ throws RemoteException {
+ // GIVEN shouldTriggerActiveUnlock
+ when(mAuthController.isUdfpsFingerDown()).thenReturn(false);
+ keyguardIsVisible();
+ keyguardNotGoingAway();
+ statusBarShadeIsNotLocked();
+ when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+
+ // GIVEN active unlock triggers on biometric failures
+ when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+ ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL))
+ .thenReturn(true);
+
+ // WHEN face fails & bypass is not allowed
+ lockscreenBypassIsAllowed();
+ mKeyguardUpdateMonitor.mFaceAuthenticationCallback.onAuthenticationFailed();
+
+ // THEN request unlock with a keyguard dismissal
+ verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+ eq(true));
+ }
+
+ @Test
+ public void faceNonBypassFailure_requestActiveUnlock_dismissKeyguard()
+ throws RemoteException {
+ // GIVEN shouldTriggerActiveUnlock
+ when(mAuthController.isUdfpsFingerDown()).thenReturn(false);
+ lockscreenBypassIsNotAllowed();
+ when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+
+ // GIVEN active unlock triggers on biometric failures
+ when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+ ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL))
+ .thenReturn(true);
+
+ // WHEN face fails & on the bouncer
+ bouncerFullyVisible();
+ mKeyguardUpdateMonitor.mFaceAuthenticationCallback.onAuthenticationFailed();
+
+ // THEN request unlock with a keyguard dismissal
+ verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+ eq(true));
+ }
+
private void userDeviceLockDown() {
when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
when(mStrongAuthTracker.getStrongAuthForUser(mCurrentUserId))
@@ -2100,6 +2190,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
}
private void mockCanBypassLockscreen(boolean canBypass) {
+ // force update the isFaceEnrolled cache:
+ mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(getCurrentUser());
+
mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
when(mKeyguardBypassController.canBypass()).thenReturn(canBypass);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
index ca94ea826782..262b4b889f84 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
@@ -301,7 +301,7 @@ class CameraGestureHelperTest : SysuiTestCase() {
val intent = intentCaptor.value
assertThat(CameraIntents.isSecureCameraIntent(intent)).isEqualTo(isSecure)
- assertThat(intent.getIntExtra(CameraGestureHelper.EXTRA_CAMERA_LAUNCH_SOURCE, -1))
+ assertThat(intent.getIntExtra(CameraIntents.EXTRA_LAUNCH_SOURCE, -1))
.isEqualTo(source)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
index 779788aa0075..d172c9a2e630 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
@@ -38,6 +38,7 @@ import com.android.systemui.controls.CustomIconCache
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.controller.StructureInfo
import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.management.ControlsProviderSelectorActivity
import com.android.systemui.controls.settings.FakeControlsSettingsRepository
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
@@ -53,6 +54,7 @@ import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.android.wm.shell.TaskView
import com.android.wm.shell.TaskViewFactory
@@ -322,6 +324,45 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
.isFalse()
}
+ @Test
+ fun testResolveActivityWhileSeeding_ControlsActivity() {
+ whenever(controlsController.addSeedingFavoritesCallback(any())).thenReturn(true)
+ assertThat(underTest.resolveActivity()).isEqualTo(ControlsActivity::class.java)
+ }
+
+ @Test
+ fun testResolveActivityNotSeedingNoFavoritesNoPanels_ControlsProviderSelectorActivity() {
+ whenever(controlsController.addSeedingFavoritesCallback(any())).thenReturn(false)
+ whenever(controlsController.getFavorites()).thenReturn(emptyList())
+
+ val selectedItems =
+ listOf(
+ SelectedItem.StructureItem(
+ StructureInfo(ComponentName.unflattenFromString("pkg/.cls1"), "a", ArrayList())
+ ),
+ )
+ sharedPreferences
+ .edit()
+ .putString("controls_component", selectedItems[0].componentName.flattenToString())
+ .putString("controls_structure", selectedItems[0].name.toString())
+ .commit()
+
+ assertThat(underTest.resolveActivity())
+ .isEqualTo(ControlsProviderSelectorActivity::class.java)
+ }
+
+ @Test
+ fun testResolveActivityNotSeedingNoDefaultNoFavoritesPanel_ControlsActivity() {
+ val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
+ val activity = ComponentName("pkg", "activity")
+ val csi = ControlsServiceInfo(panel.componentName, panel.appName, activity)
+ whenever(controlsController.addSeedingFavoritesCallback(any())).thenReturn(true)
+ whenever(controlsController.getFavorites()).thenReturn(emptyList())
+ whenever(controlsListingController.getCurrentServices()).thenReturn(listOf(csi))
+
+ assertThat(underTest.resolveActivity()).isEqualTo(ControlsActivity::class.java)
+ }
+
private fun setUpPanel(panel: SelectedItem.PanelItem): ControlsServiceInfo {
val activity = ComponentName("pkg", "activity")
sharedPreferences
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
index 5cd2ace4604a..de04ef810dd0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
@@ -75,6 +75,7 @@ class PanelTaskViewControllerTest : SysuiTestCase() {
uiExecutor.execute(it.arguments[0] as Runnable)
true
}
+ whenever(activityContext.resources).thenReturn(context.resources)
uiExecutor = FakeExecutor(FakeSystemClock())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
index 99406ed44606..8e689cf8f17e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
@@ -23,11 +23,11 @@ import org.mockito.MockitoAnnotations
class DreamOverlayAnimationsControllerTest : SysuiTestCase() {
companion object {
+ private const val DREAM_BLUR_RADIUS = 50
private const val DREAM_IN_BLUR_ANIMATION_DURATION = 1L
- private const val DREAM_IN_BLUR_ANIMATION_DELAY = 2L
private const val DREAM_IN_COMPLICATIONS_ANIMATION_DURATION = 3L
- private const val DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY = 4L
- private const val DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY = 5L
+ private const val DREAM_IN_TRANSLATION_Y_DISTANCE = 6
+ private const val DREAM_IN_TRANSLATION_Y_DURATION = 7L
private const val DREAM_OUT_TRANSLATION_Y_DISTANCE = 6
private const val DREAM_OUT_TRANSLATION_Y_DURATION = 7L
private const val DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM = 8L
@@ -54,11 +54,11 @@ class DreamOverlayAnimationsControllerTest : SysuiTestCase() {
hostViewController,
statusBarViewController,
stateController,
+ DREAM_BLUR_RADIUS,
DREAM_IN_BLUR_ANIMATION_DURATION,
- DREAM_IN_BLUR_ANIMATION_DELAY,
DREAM_IN_COMPLICATIONS_ANIMATION_DURATION,
- DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY,
- DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY,
+ DREAM_IN_TRANSLATION_Y_DISTANCE,
+ DREAM_IN_TRANSLATION_Y_DURATION,
DREAM_OUT_TRANSLATION_Y_DISTANCE,
DREAM_OUT_TRANSLATION_Y_DURATION,
DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
index fdb4cc4480da..e414942afb56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
@@ -17,6 +17,10 @@ package com.android.systemui.dreams.complication;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
@@ -29,6 +33,7 @@ import org.junit.runner.RunWith;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.function.Consumer;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -197,4 +202,19 @@ public class ComplicationLayoutParamsTest extends SysuiTestCase {
assertThat(paramsWithConstraint.constraintSpecified()).isTrue();
assertThat(paramsWithConstraint.getConstraint()).isEqualTo(constraint);
}
+
+ @Test
+ public void testIteratePositions() {
+ final int positions = ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_START
+ | ComplicationLayoutParams.POSITION_END;
+ final Consumer<Integer> consumer = mock(Consumer.class);
+
+ ComplicationLayoutParams.iteratePositions(consumer, positions);
+
+ verify(consumer).accept(ComplicationLayoutParams.POSITION_TOP);
+ verify(consumer).accept(ComplicationLayoutParams.POSITION_START);
+ verify(consumer).accept(ComplicationLayoutParams.POSITION_END);
+ verify(consumer, never()).accept(ComplicationLayoutParams.POSITION_BOTTOM);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
index e6d3a69593cd..89c728082cc5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
@@ -27,6 +27,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.ComponentName;
import android.content.Context;
import android.testing.AndroidTestingRunner;
import android.view.View;
@@ -54,6 +55,7 @@ import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@@ -147,6 +149,19 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase {
}
@Test
+ public void complicationAvailability_serviceAvailable_noFavorites_panel_addComplication() {
+ final DreamHomeControlsComplication.Registrant registrant =
+ new DreamHomeControlsComplication.Registrant(mComplication,
+ mDreamOverlayStateController, mControlsComponent);
+ registrant.start();
+
+ setHaveFavorites(false);
+ setServiceWithPanel();
+
+ verify(mDreamOverlayStateController).addComplication(mComplication);
+ }
+
+ @Test
public void complicationAvailability_serviceNotAvailable_haveFavorites_doNotAddComplication() {
final DreamHomeControlsComplication.Registrant registrant =
new DreamHomeControlsComplication.Registrant(mComplication,
@@ -232,6 +247,15 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase {
triggerControlsListingCallback(serviceInfos);
}
+ private void setServiceWithPanel() {
+ final List<ControlsServiceInfo> serviceInfos = new ArrayList<>();
+ ControlsServiceInfo csi = mock(ControlsServiceInfo.class);
+ serviceInfos.add(csi);
+ when(csi.getPanelActivity()).thenReturn(new ComponentName("a", "b"));
+ when(mControlsListingController.getCurrentServices()).thenReturn(serviceInfos);
+ triggerControlsListingCallback(serviceInfos);
+ }
+
private void setDreamOverlayActive(boolean value) {
when(mDreamOverlayStateController.isOverlayActive()).thenReturn(value);
verify(mDreamOverlayStateController).addCallback(mStateCallbackCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
index cef452b8ec22..09c8e6ac1268 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
@@ -20,7 +20,13 @@ package com.android.systemui.keyguard
import android.content.ContentValues
import android.content.pm.PackageManager
import android.content.pm.ProviderInfo
+import android.os.Bundle
+import android.os.Handler
+import android.os.IBinder
import android.os.UserHandle
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.SurfaceControlViewHost
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SystemUIAppComponentFactoryBase
@@ -36,6 +42,9 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
+import com.android.systemui.keyguard.ui.preview.KeyguardPreviewRenderer
+import com.android.systemui.keyguard.ui.preview.KeyguardPreviewRendererFactory
+import com.android.systemui.keyguard.ui.preview.KeyguardRemotePreviewManager
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
@@ -43,40 +52,53 @@ import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordance
import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.FakeSharedPreferences
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
@Mock private lateinit var lockPatternUtils: LockPatternUtils
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var previewRendererFactory: KeyguardPreviewRendererFactory
+ @Mock private lateinit var previewRenderer: KeyguardPreviewRenderer
+ @Mock private lateinit var backgroundHandler: Handler
+ @Mock private lateinit var previewSurfacePackage: SurfaceControlViewHost.SurfacePackage
private lateinit var underTest: KeyguardQuickAffordanceProvider
+ private lateinit var testScope: TestScope
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ whenever(previewRenderer.surfacePackage).thenReturn(previewSurfacePackage)
+ whenever(previewRendererFactory.create(any())).thenReturn(previewRenderer)
+ whenever(backgroundHandler.looper).thenReturn(TestableLooper.get(this).looper)
underTest = KeyguardQuickAffordanceProvider()
- val scope = CoroutineScope(IMMEDIATE)
+ val testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
val localUserSelectionManager =
KeyguardQuickAffordanceLocalUserSelectionManager(
context = context,
@@ -96,7 +118,7 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
)
val remoteUserSelectionManager =
KeyguardQuickAffordanceRemoteUserSelectionManager(
- scope = scope,
+ scope = testScope.backgroundScope,
userTracker = userTracker,
clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
userHandle = UserHandle.SYSTEM,
@@ -104,7 +126,7 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
appContext = context,
- scope = scope,
+ scope = testScope.backgroundScope,
localUserSelectionManager = localUserSelectionManager,
remoteUserSelectionManager = remoteUserSelectionManager,
userTracker = userTracker,
@@ -123,8 +145,8 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
),
legacySettingSyncer =
KeyguardQuickAffordanceLegacySettingSyncer(
- scope = scope,
- backgroundDispatcher = IMMEDIATE,
+ scope = testScope.backgroundScope,
+ backgroundDispatcher = testDispatcher,
secureSettings = FakeSettings(),
selectionsManager = localUserSelectionManager,
),
@@ -148,6 +170,12 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
},
repository = { quickAffordanceRepository },
)
+ underTest.previewManager =
+ KeyguardRemotePreviewManager(
+ previewRendererFactory = previewRendererFactory,
+ mainDispatcher = testDispatcher,
+ backgroundHandler = backgroundHandler,
+ )
underTest.attachInfoForTesting(
context,
@@ -190,7 +218,7 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
@Test
fun `insert and query selection`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
val affordanceId = AFFORDANCE_2
val affordanceName = AFFORDANCE_2_NAME
@@ -214,7 +242,7 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
@Test
fun `query slots`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
assertThat(querySlots())
.isEqualTo(
listOf(
@@ -232,7 +260,7 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
@Test
fun `query affordances`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
assertThat(queryAffordances())
.isEqualTo(
listOf(
@@ -252,7 +280,7 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
@Test
fun `delete and query selection`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
insertSelection(
slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
affordanceId = AFFORDANCE_1,
@@ -286,7 +314,7 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
@Test
fun `delete all selections in a slot`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
insertSelection(
slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
affordanceId = AFFORDANCE_1,
@@ -316,6 +344,23 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
)
}
+ @Test
+ fun preview() =
+ testScope.runTest {
+ val hostToken: IBinder = mock()
+ whenever(previewRenderer.hostToken).thenReturn(hostToken)
+ val extras = Bundle()
+
+ val result = underTest.call("whatever", "anything", extras)
+
+ verify(previewRenderer).render()
+ verify(hostToken).linkToDeath(any(), anyInt())
+ assertThat(result!!).isNotNull()
+ assertThat(result.get(KeyguardRemotePreviewManager.KEY_PREVIEW_SURFACE_PACKAGE))
+ .isEqualTo(previewSurfacePackage)
+ assertThat(result.containsKey(KeyguardRemotePreviewManager.KEY_PREVIEW_CALLBACK))
+ }
+
private fun insertSelection(
slotId: String,
affordanceId: String,
@@ -451,7 +496,6 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
)
companion object {
- private val IMMEDIATE = Dispatchers.Main.immediate
private const val AFFORDANCE_1 = "affordance_1"
private const val AFFORDANCE_2 = "affordance_2"
private const val AFFORDANCE_1_NAME = "affordance_1_name"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
index 322014a61a73..f8cb40885d21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
@@ -20,13 +20,14 @@ package com.android.systemui.keyguard.data.quickaffordance
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import java.util.*
+import java.util.Optional
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -50,20 +51,22 @@ class HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest : SysuiTes
companion object {
@Parameters(
name =
- "feature enabled = {0}, has favorites = {1}, has service infos = {2}, can show" +
- " while locked = {3}, visibility is AVAILABLE {4} - expected visible = {5}"
+ "feature enabled = {0}, has favorites = {1}, has panels = {2}, " +
+ "has service infos = {3}, can show while locked = {4}, " +
+ "visibility is AVAILABLE {5} - expected visible = {6}"
)
@JvmStatic
fun data() =
- (0 until 32)
+ (0 until 64)
.map { combination ->
arrayOf(
- /* isFeatureEnabled= */ combination and 0b10000 != 0,
- /* hasFavorites= */ combination and 0b01000 != 0,
- /* hasServiceInfos= */ combination and 0b00100 != 0,
- /* canShowWhileLocked= */ combination and 0b00010 != 0,
- /* visibilityAvailable= */ combination and 0b00001 != 0,
- /* isVisible= */ combination == 0b11111,
+ /* isFeatureEnabled= */ combination and 0b100000 != 0,
+ /* hasFavorites = */ combination and 0b010000 != 0,
+ /* hasPanels = */ combination and 0b001000 != 0,
+ /* hasServiceInfos= */ combination and 0b000100 != 0,
+ /* canShowWhileLocked= */ combination and 0b000010 != 0,
+ /* visibilityAvailable= */ combination and 0b000001 != 0,
+ /* isVisible= */ combination in setOf(0b111111, 0b110111, 0b101111),
)
}
.toList()
@@ -72,6 +75,7 @@ class HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest : SysuiTes
@Mock private lateinit var component: ControlsComponent
@Mock private lateinit var controlsController: ControlsController
@Mock private lateinit var controlsListingController: ControlsListingController
+ @Mock private lateinit var controlsServiceInfo: ControlsServiceInfo
@Captor
private lateinit var callbackCaptor:
ArgumentCaptor<ControlsListingController.ControlsListingCallback>
@@ -80,10 +84,11 @@ class HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest : SysuiTes
@JvmField @Parameter(0) var isFeatureEnabled: Boolean = false
@JvmField @Parameter(1) var hasFavorites: Boolean = false
- @JvmField @Parameter(2) var hasServiceInfos: Boolean = false
- @JvmField @Parameter(3) var canShowWhileLocked: Boolean = false
- @JvmField @Parameter(4) var isVisibilityAvailable: Boolean = false
- @JvmField @Parameter(5) var isVisibleExpected: Boolean = false
+ @JvmField @Parameter(2) var hasPanels: Boolean = false
+ @JvmField @Parameter(3) var hasServiceInfos: Boolean = false
+ @JvmField @Parameter(4) var canShowWhileLocked: Boolean = false
+ @JvmField @Parameter(5) var isVisibilityAvailable: Boolean = false
+ @JvmField @Parameter(6) var isVisibleExpected: Boolean = false
@Before
fun setUp() {
@@ -93,10 +98,13 @@ class HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest : SysuiTes
whenever(component.getControlsController()).thenReturn(Optional.of(controlsController))
whenever(component.getControlsListingController())
.thenReturn(Optional.of(controlsListingController))
+ if (hasPanels) {
+ whenever(controlsServiceInfo.panelActivity).thenReturn(mock())
+ }
whenever(controlsListingController.getCurrentServices())
.thenReturn(
if (hasServiceInfos) {
- listOf(mock(), mock())
+ listOf(controlsServiceInfo, mock())
} else {
emptyList()
}
@@ -134,10 +142,15 @@ class HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest : SysuiTes
val job = underTest.lockScreenState.onEach(values::add).launchIn(this)
if (canShowWhileLocked) {
+ val serviceInfoMock: ControlsServiceInfo = mock {
+ if (hasPanels) {
+ whenever(panelActivity).thenReturn(mock())
+ }
+ }
verify(controlsListingController).addCallback(callbackCaptor.capture())
callbackCaptor.value.onServicesUpdated(
if (hasServiceInfos) {
- listOf(mock())
+ listOf(serviceInfoMock)
} else {
emptyList()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 11fe905b1d1f..d97571bcd8ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -23,6 +23,7 @@ import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
@@ -49,14 +50,10 @@ import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.runBlockingTest
-import kotlinx.coroutines.yield
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -78,6 +75,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
private lateinit var underTest: KeyguardQuickAffordanceInteractor
+ private lateinit var testScope: TestScope
private lateinit var repository: FakeKeyguardRepository
private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
private lateinit var quickAccessWallet: FakeKeyguardQuickAffordanceConfig
@@ -99,7 +97,8 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
)
qrCodeScanner =
FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
- val scope = CoroutineScope(IMMEDIATE)
+ val testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
val localUserSelectionManager =
KeyguardQuickAffordanceLocalUserSelectionManager(
@@ -120,7 +119,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
)
val remoteUserSelectionManager =
KeyguardQuickAffordanceRemoteUserSelectionManager(
- scope = scope,
+ scope = testScope.backgroundScope,
userTracker = userTracker,
clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
userHandle = UserHandle.SYSTEM,
@@ -128,14 +127,14 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
appContext = context,
- scope = scope,
+ scope = testScope.backgroundScope,
localUserSelectionManager = localUserSelectionManager,
remoteUserSelectionManager = remoteUserSelectionManager,
userTracker = userTracker,
legacySettingSyncer =
KeyguardQuickAffordanceLegacySettingSyncer(
- scope = scope,
- backgroundDispatcher = IMMEDIATE,
+ scope = testScope.backgroundScope,
+ backgroundDispatcher = testDispatcher,
secureSettings = FakeSettings(),
selectionsManager = localUserSelectionManager,
),
@@ -175,88 +174,76 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
}
@Test
- fun `quickAffordance - bottom start affordance is visible`() = runBlockingTest {
- val configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
- homeControls.setState(
- KeyguardQuickAffordanceConfig.LockScreenState.Visible(
- icon = ICON,
- activationState = ActivationState.Active,
+ fun `quickAffordance - bottom start affordance is visible`() =
+ testScope.runTest {
+ val configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
+ homeControls.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon = ICON,
+ activationState = ActivationState.Active,
+ )
)
- )
-
- var latest: KeyguardQuickAffordanceModel? = null
- val job =
- underTest
- .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
- .onEach { latest = it }
- .launchIn(this)
- // The interactor has an onStart { emit(Hidden) } to cover for upstream configs that don't
- // produce an initial value. We yield to give the coroutine time to emit the first real
- // value from our config.
- yield()
-
- assertThat(latest).isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
- val visibleModel = latest as KeyguardQuickAffordanceModel.Visible
- assertThat(visibleModel.configKey).isEqualTo(configKey)
- assertThat(visibleModel.icon).isEqualTo(ICON)
- assertThat(visibleModel.icon.contentDescription)
- .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
- assertThat(visibleModel.activationState).isEqualTo(ActivationState.Active)
- job.cancel()
- }
+
+ val collectedValue =
+ collectLastValue(
+ underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
+ )
+
+ assertThat(collectedValue())
+ .isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
+ val visibleModel = collectedValue() as KeyguardQuickAffordanceModel.Visible
+ assertThat(visibleModel.configKey).isEqualTo(configKey)
+ assertThat(visibleModel.icon).isEqualTo(ICON)
+ assertThat(visibleModel.icon.contentDescription)
+ .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
+ assertThat(visibleModel.activationState).isEqualTo(ActivationState.Active)
+ }
@Test
- fun `quickAffordance - bottom end affordance is visible`() = runBlockingTest {
- val configKey = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
- quickAccessWallet.setState(
- KeyguardQuickAffordanceConfig.LockScreenState.Visible(
- icon = ICON,
+ fun `quickAffordance - bottom end affordance is visible`() =
+ testScope.runTest {
+ val configKey = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+ quickAccessWallet.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon = ICON,
+ )
)
- )
-
- var latest: KeyguardQuickAffordanceModel? = null
- val job =
- underTest
- .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
- .onEach { latest = it }
- .launchIn(this)
- // The interactor has an onStart { emit(Hidden) } to cover for upstream configs that don't
- // produce an initial value. We yield to give the coroutine time to emit the first real
- // value from our config.
- yield()
-
- assertThat(latest).isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
- val visibleModel = latest as KeyguardQuickAffordanceModel.Visible
- assertThat(visibleModel.configKey).isEqualTo(configKey)
- assertThat(visibleModel.icon).isEqualTo(ICON)
- assertThat(visibleModel.icon.contentDescription)
- .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
- assertThat(visibleModel.activationState).isEqualTo(ActivationState.NotSupported)
- job.cancel()
- }
+
+ val collectedValue =
+ collectLastValue(
+ underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
+ )
+
+ assertThat(collectedValue())
+ .isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
+ val visibleModel = collectedValue() as KeyguardQuickAffordanceModel.Visible
+ assertThat(visibleModel.configKey).isEqualTo(configKey)
+ assertThat(visibleModel.icon).isEqualTo(ICON)
+ assertThat(visibleModel.icon.contentDescription)
+ .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
+ assertThat(visibleModel.activationState).isEqualTo(ActivationState.NotSupported)
+ }
@Test
- fun `quickAffordance - bottom start affordance hidden while dozing`() = runBlockingTest {
- repository.setDozing(true)
- homeControls.setState(
- KeyguardQuickAffordanceConfig.LockScreenState.Visible(
- icon = ICON,
+ fun `quickAffordance - bottom start affordance hidden while dozing`() =
+ testScope.runTest {
+ repository.setDozing(true)
+ homeControls.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon = ICON,
+ )
)
- )
-
- var latest: KeyguardQuickAffordanceModel? = null
- val job =
- underTest
- .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
- .onEach { latest = it }
- .launchIn(this)
- assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
- job.cancel()
- }
+
+ val collectedValue =
+ collectLastValue(
+ underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
+ )
+ assertThat(collectedValue()).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
+ }
@Test
fun `quickAffordance - bottom start affordance hidden when lockscreen is not showing`() =
- runBlockingTest {
+ testScope.runTest {
repository.setKeyguardShowing(false)
homeControls.setState(
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
@@ -264,19 +251,45 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
)
)
- var latest: KeyguardQuickAffordanceModel? = null
- val job =
- underTest
- .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
- .onEach { latest = it }
- .launchIn(this)
- assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
- job.cancel()
+ val collectedValue =
+ collectLastValue(
+ underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
+ )
+ assertThat(collectedValue()).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
+ }
+
+ @Test
+ fun `quickAffordanceAlwaysVisible - even when lock screen not showing and dozing`() =
+ testScope.runTest {
+ repository.setKeyguardShowing(false)
+ repository.setDozing(true)
+ val configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
+ homeControls.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon = ICON,
+ activationState = ActivationState.Active,
+ )
+ )
+
+ val collectedValue =
+ collectLastValue(
+ underTest.quickAffordanceAlwaysVisible(
+ KeyguardQuickAffordancePosition.BOTTOM_START
+ )
+ )
+ assertThat(collectedValue())
+ .isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
+ val visibleModel = collectedValue() as KeyguardQuickAffordanceModel.Visible
+ assertThat(visibleModel.configKey).isEqualTo(configKey)
+ assertThat(visibleModel.icon).isEqualTo(ICON)
+ assertThat(visibleModel.icon.contentDescription)
+ .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
+ assertThat(visibleModel.activationState).isEqualTo(ActivationState.Active)
}
@Test
fun select() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
homeControls.setState(
KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
@@ -296,23 +309,18 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
)
)
- var startConfig: KeyguardQuickAffordanceModel? = null
- val job1 =
- underTest
- .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
- .onEach { startConfig = it }
- .launchIn(this)
- var endConfig: KeyguardQuickAffordanceModel? = null
- val job2 =
- underTest
- .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
- .onEach { endConfig = it }
- .launchIn(this)
+ val startConfig =
+ collectLastValue(
+ underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
+ )
+ val endConfig =
+ collectLastValue(
+ underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
+ )
underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
- yield()
- yield()
- assertThat(startConfig)
+
+ assertThat(startConfig())
.isEqualTo(
KeyguardQuickAffordanceModel.Visible(
configKey =
@@ -322,7 +330,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
activationState = ActivationState.NotSupported,
)
)
- assertThat(endConfig)
+ assertThat(endConfig())
.isEqualTo(
KeyguardQuickAffordanceModel.Hidden,
)
@@ -345,9 +353,8 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
quickAccessWallet.key
)
- yield()
- yield()
- assertThat(startConfig)
+
+ assertThat(startConfig())
.isEqualTo(
KeyguardQuickAffordanceModel.Visible(
configKey =
@@ -357,7 +364,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
activationState = ActivationState.NotSupported,
)
)
- assertThat(endConfig)
+ assertThat(endConfig())
.isEqualTo(
KeyguardQuickAffordanceModel.Hidden,
)
@@ -377,9 +384,8 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
)
underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, qrCodeScanner.key)
- yield()
- yield()
- assertThat(startConfig)
+
+ assertThat(startConfig())
.isEqualTo(
KeyguardQuickAffordanceModel.Visible(
configKey =
@@ -389,7 +395,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
activationState = ActivationState.NotSupported,
)
)
- assertThat(endConfig)
+ assertThat(endConfig())
.isEqualTo(
KeyguardQuickAffordanceModel.Visible(
configKey =
@@ -420,14 +426,11 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
),
)
)
-
- job1.cancel()
- job2.cancel()
}
@Test
fun `unselect - one`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
homeControls.setState(
KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
@@ -439,34 +442,23 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
)
- var startConfig: KeyguardQuickAffordanceModel? = null
- val job1 =
- underTest
- .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
- .onEach { startConfig = it }
- .launchIn(this)
- var endConfig: KeyguardQuickAffordanceModel? = null
- val job2 =
- underTest
- .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
- .onEach { endConfig = it }
- .launchIn(this)
+ val startConfig =
+ collectLastValue(
+ underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
+ )
+ val endConfig =
+ collectLastValue(
+ underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
+ )
underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
- yield()
- yield()
underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, quickAccessWallet.key)
- yield()
- yield()
-
underTest.unselect(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
- yield()
- yield()
- assertThat(startConfig)
+ assertThat(startConfig())
.isEqualTo(
KeyguardQuickAffordanceModel.Hidden,
)
- assertThat(endConfig)
+ assertThat(endConfig())
.isEqualTo(
KeyguardQuickAffordanceModel.Visible(
configKey =
@@ -495,14 +487,12 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
quickAccessWallet.key
)
- yield()
- yield()
- assertThat(startConfig)
+ assertThat(startConfig())
.isEqualTo(
KeyguardQuickAffordanceModel.Hidden,
)
- assertThat(endConfig)
+ assertThat(endConfig())
.isEqualTo(
KeyguardQuickAffordanceModel.Hidden,
)
@@ -513,14 +503,11 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
)
)
-
- job1.cancel()
- job2.cancel()
}
@Test
fun `unselect - all`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
homeControls.setState(
KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
@@ -533,15 +520,8 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
)
underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
- yield()
- yield()
underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, quickAccessWallet.key)
- yield()
- yield()
-
underTest.unselect(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, null)
- yield()
- yield()
assertThat(underTest.getSelections())
.isEqualTo(
@@ -562,8 +542,6 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
null,
)
- yield()
- yield()
assertThat(underTest.getSelections())
.isEqualTo(
@@ -584,6 +562,5 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
)
}
private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
- private val IMMEDIATE = Dispatchers.Main.immediate
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 83a5d0e90c84..0abff88b5faf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -23,6 +23,7 @@ import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.doze.util.BurnInHelperWrapper
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
@@ -44,20 +45,21 @@ import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAfforda
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlin.math.max
import kotlin.math.min
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.test.runBlockingTest
-import kotlinx.coroutines.yield
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -67,9 +69,9 @@ import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
@@ -83,6 +85,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
private lateinit var underTest: KeyguardBottomAreaViewModel
+ private lateinit var testScope: TestScope
private lateinit var repository: FakeKeyguardRepository
private lateinit var registry: FakeKeyguardQuickAffordanceRegistry
private lateinit var homeControlsQuickAffordanceConfig: FakeKeyguardQuickAffordanceConfig
@@ -123,7 +126,8 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
whenever(userTracker.userHandle).thenReturn(mock())
whenever(lockPatternUtils.getStrongAuthForUser(anyInt()))
.thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
- val scope = CoroutineScope(IMMEDIATE)
+ val testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
val localUserSelectionManager =
KeyguardQuickAffordanceLocalUserSelectionManager(
context = context,
@@ -143,7 +147,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
)
val remoteUserSelectionManager =
KeyguardQuickAffordanceRemoteUserSelectionManager(
- scope = scope,
+ scope = testScope.backgroundScope,
userTracker = userTracker,
clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
userHandle = UserHandle.SYSTEM,
@@ -151,14 +155,14 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
appContext = context,
- scope = scope,
+ scope = testScope.backgroundScope,
localUserSelectionManager = localUserSelectionManager,
remoteUserSelectionManager = remoteUserSelectionManager,
userTracker = userTracker,
legacySettingSyncer =
KeyguardQuickAffordanceLegacySettingSyncer(
- scope = scope,
- backgroundDispatcher = IMMEDIATE,
+ scope = testScope.backgroundScope,
+ backgroundDispatcher = testDispatcher,
secureSettings = FakeSettings(),
selectionsManager = localUserSelectionManager,
),
@@ -194,366 +198,394 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
}
@Test
- fun `startButton - present - visible model - starts activity on click`() = runBlockingTest {
- repository.setKeyguardShowing(true)
- var latest: KeyguardQuickAffordanceViewModel? = null
- val job = underTest.startButton.onEach { latest = it }.launchIn(this)
-
- val testConfig =
- TestConfig(
- isVisible = true,
- isClickable = true,
- isActivated = true,
- icon = mock(),
- canShowWhileLocked = false,
- intent = Intent("action"),
- )
- val configKey =
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ fun `startButton - present - visible model - starts activity on click`() =
+ testScope.runTest {
+ repository.setKeyguardShowing(true)
+ val latest = collectLastValue(underTest.startButton)
+
+ val testConfig =
+ TestConfig(
+ isVisible = true,
+ isClickable = true,
+ isActivated = true,
+ icon = mock(),
+ canShowWhileLocked = false,
+ intent = Intent("action"),
+ )
+ val configKey =
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig = testConfig,
+ )
+
+ assertQuickAffordanceViewModel(
+ viewModel = latest(),
testConfig = testConfig,
+ configKey = configKey,
)
-
- assertQuickAffordanceViewModel(
- viewModel = latest,
- testConfig = testConfig,
- configKey = configKey,
- )
- job.cancel()
- }
+ }
@Test
- fun `endButton - present - visible model - do nothing on click`() = runBlockingTest {
- repository.setKeyguardShowing(true)
- var latest: KeyguardQuickAffordanceViewModel? = null
- val job = underTest.endButton.onEach { latest = it }.launchIn(this)
+ fun `startButton - in preview mode - visible even when keyguard not showing`() =
+ testScope.runTest {
+ underTest.enablePreviewMode(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START)
+ repository.setKeyguardShowing(false)
+ val latest = collectLastValue(underTest.startButton)
+
+ val icon: Icon = mock()
+ val configKey =
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig =
+ TestConfig(
+ isVisible = true,
+ isClickable = true,
+ isActivated = true,
+ icon = icon,
+ canShowWhileLocked = false,
+ intent = Intent("action"),
+ ),
+ )
- val config =
- TestConfig(
- isVisible = true,
- isClickable = true,
- icon = mock(),
- canShowWhileLocked = false,
- intent = null, // This will cause it to tell the system that the click was handled.
+ assertQuickAffordanceViewModel(
+ viewModel = latest(),
+ testConfig =
+ TestConfig(
+ isVisible = true,
+ isClickable = false,
+ isActivated = true,
+ icon = icon,
+ canShowWhileLocked = false,
+ intent = Intent("action"),
+ ),
+ configKey = configKey,
)
- val configKey =
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_END,
+ assertThat(latest()?.isSelected).isTrue()
+ }
+
+ @Test
+ fun `endButton - present - visible model - do nothing on click`() =
+ testScope.runTest {
+ repository.setKeyguardShowing(true)
+ val latest = collectLastValue(underTest.endButton)
+
+ val config =
+ TestConfig(
+ isVisible = true,
+ isClickable = true,
+ icon = mock(),
+ canShowWhileLocked = false,
+ intent =
+ null, // This will cause it to tell the system that the click was handled.
+ )
+ val configKey =
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_END,
+ testConfig = config,
+ )
+
+ assertQuickAffordanceViewModel(
+ viewModel = latest(),
testConfig = config,
+ configKey = configKey,
)
-
- assertQuickAffordanceViewModel(
- viewModel = latest,
- testConfig = config,
- configKey = configKey,
- )
- job.cancel()
- }
+ }
@Test
- fun `startButton - not present - model is hidden`() = runBlockingTest {
- var latest: KeyguardQuickAffordanceViewModel? = null
- val job = underTest.startButton.onEach { latest = it }.launchIn(this)
+ fun `startButton - not present - model is hidden`() =
+ testScope.runTest {
+ val latest = collectLastValue(underTest.startButton)
- val config =
- TestConfig(
- isVisible = false,
- )
- val configKey =
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ val config =
+ TestConfig(
+ isVisible = false,
+ )
+ val configKey =
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig = config,
+ )
+
+ assertQuickAffordanceViewModel(
+ viewModel = latest(),
testConfig = config,
+ configKey = configKey,
)
-
- assertQuickAffordanceViewModel(
- viewModel = latest,
- testConfig = config,
- configKey = configKey,
- )
- job.cancel()
- }
+ }
@Test
- fun animateButtonReveal() = runBlockingTest {
- repository.setKeyguardShowing(true)
- val testConfig =
- TestConfig(
- isVisible = true,
- isClickable = true,
- icon = mock(),
- canShowWhileLocked = false,
- intent = Intent("action"),
+ fun animateButtonReveal() =
+ testScope.runTest {
+ repository.setKeyguardShowing(true)
+ val testConfig =
+ TestConfig(
+ isVisible = true,
+ isClickable = true,
+ icon = mock(),
+ canShowWhileLocked = false,
+ intent = Intent("action"),
+ )
+
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig = testConfig,
)
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_START,
- testConfig = testConfig,
- )
+ val value = collectLastValue(underTest.startButton.map { it.animateReveal })
- val values = mutableListOf<Boolean>()
- val job = underTest.startButton.onEach { values.add(it.animateReveal) }.launchIn(this)
+ assertThat(value()).isFalse()
+ repository.setAnimateDozingTransitions(true)
+ assertThat(value()).isTrue()
+ repository.setAnimateDozingTransitions(false)
+ assertThat(value()).isFalse()
+ }
- repository.setAnimateDozingTransitions(true)
- yield()
- repository.setAnimateDozingTransitions(false)
- yield()
+ @Test
+ fun isOverlayContainerVisible() =
+ testScope.runTest {
+ val value = collectLastValue(underTest.isOverlayContainerVisible)
+
+ assertThat(value()).isTrue()
+ repository.setDozing(true)
+ assertThat(value()).isFalse()
+ repository.setDozing(false)
+ assertThat(value()).isTrue()
+ }
- // Note the extra false value in the beginning. This is to cover for the initial value
- // inserted by the quick affordance interactor which it does to cover for config
- // implementations that don't emit an initial value.
- assertThat(values).isEqualTo(listOf(false, false, true, false))
- job.cancel()
- }
+ @Test
+ fun alpha() =
+ testScope.runTest {
+ val value = collectLastValue(underTest.alpha)
+
+ assertThat(value()).isEqualTo(1f)
+ repository.setBottomAreaAlpha(0.1f)
+ assertThat(value()).isEqualTo(0.1f)
+ repository.setBottomAreaAlpha(0.5f)
+ assertThat(value()).isEqualTo(0.5f)
+ repository.setBottomAreaAlpha(0.2f)
+ assertThat(value()).isEqualTo(0.2f)
+ repository.setBottomAreaAlpha(0f)
+ assertThat(value()).isEqualTo(0f)
+ }
@Test
- fun isOverlayContainerVisible() = runBlockingTest {
- val values = mutableListOf<Boolean>()
- val job = underTest.isOverlayContainerVisible.onEach(values::add).launchIn(this)
+ fun `alpha - in preview mode - does not change`() =
+ testScope.runTest {
+ underTest.enablePreviewMode(null)
+ val value = collectLastValue(underTest.alpha)
+
+ assertThat(value()).isEqualTo(1f)
+ repository.setBottomAreaAlpha(0.1f)
+ assertThat(value()).isEqualTo(1f)
+ repository.setBottomAreaAlpha(0.5f)
+ assertThat(value()).isEqualTo(1f)
+ repository.setBottomAreaAlpha(0.2f)
+ assertThat(value()).isEqualTo(1f)
+ repository.setBottomAreaAlpha(0f)
+ assertThat(value()).isEqualTo(1f)
+ }
- repository.setDozing(true)
- repository.setDozing(false)
+ @Test
+ fun isIndicationAreaPadded() =
+ testScope.runTest {
+ repository.setKeyguardShowing(true)
+ val value = collectLastValue(underTest.isIndicationAreaPadded)
- assertThat(values).isEqualTo(listOf(true, false, true))
- job.cancel()
- }
+ assertThat(value()).isFalse()
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig =
+ TestConfig(
+ isVisible = true,
+ isClickable = true,
+ icon = mock(),
+ canShowWhileLocked = true,
+ )
+ )
+ assertThat(value()).isTrue()
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_END,
+ testConfig =
+ TestConfig(
+ isVisible = true,
+ isClickable = true,
+ icon = mock(),
+ canShowWhileLocked = false,
+ )
+ )
+ assertThat(value()).isTrue()
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig =
+ TestConfig(
+ isVisible = false,
+ )
+ )
+ assertThat(value()).isTrue()
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_END,
+ testConfig =
+ TestConfig(
+ isVisible = false,
+ )
+ )
+ assertThat(value()).isFalse()
+ }
@Test
- fun alpha() = runBlockingTest {
- val values = mutableListOf<Float>()
- val job = underTest.alpha.onEach(values::add).launchIn(this)
-
- repository.setBottomAreaAlpha(0.1f)
- repository.setBottomAreaAlpha(0.5f)
- repository.setBottomAreaAlpha(0.2f)
- repository.setBottomAreaAlpha(0f)
+ fun indicationAreaTranslationX() =
+ testScope.runTest {
+ val value = collectLastValue(underTest.indicationAreaTranslationX)
+
+ assertThat(value()).isEqualTo(0f)
+ repository.setClockPosition(100, 100)
+ assertThat(value()).isEqualTo(100f)
+ repository.setClockPosition(200, 100)
+ assertThat(value()).isEqualTo(200f)
+ repository.setClockPosition(200, 200)
+ assertThat(value()).isEqualTo(200f)
+ repository.setClockPosition(300, 100)
+ assertThat(value()).isEqualTo(300f)
+ }
- assertThat(values).isEqualTo(listOf(1f, 0.1f, 0.5f, 0.2f, 0f))
- job.cancel()
- }
+ @Test
+ fun indicationAreaTranslationY() =
+ testScope.runTest {
+ val value =
+ collectLastValue(underTest.indicationAreaTranslationY(DEFAULT_BURN_IN_OFFSET))
+
+ // Negative 0 - apparently there's a difference in floating point arithmetic - FML
+ assertThat(value()).isEqualTo(-0f)
+ val expected1 = setDozeAmountAndCalculateExpectedTranslationY(0.1f)
+ assertThat(value()).isEqualTo(expected1)
+ val expected2 = setDozeAmountAndCalculateExpectedTranslationY(0.2f)
+ assertThat(value()).isEqualTo(expected2)
+ val expected3 = setDozeAmountAndCalculateExpectedTranslationY(0.5f)
+ assertThat(value()).isEqualTo(expected3)
+ val expected4 = setDozeAmountAndCalculateExpectedTranslationY(1f)
+ assertThat(value()).isEqualTo(expected4)
+ }
@Test
- fun isIndicationAreaPadded() = runBlockingTest {
- repository.setKeyguardShowing(true)
- val values = mutableListOf<Boolean>()
- val job = underTest.isIndicationAreaPadded.onEach(values::add).launchIn(this)
-
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_START,
- testConfig =
- TestConfig(
- isVisible = true,
- isClickable = true,
- icon = mock(),
- canShowWhileLocked = true,
- )
- )
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_END,
- testConfig =
+ fun `isClickable - true when alpha at threshold`() =
+ testScope.runTest {
+ repository.setKeyguardShowing(true)
+ repository.setBottomAreaAlpha(
+ KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD
+ )
+
+ val testConfig =
TestConfig(
isVisible = true,
isClickable = true,
icon = mock(),
canShowWhileLocked = false,
+ intent = Intent("action"),
)
- )
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_START,
- testConfig =
- TestConfig(
- isVisible = false,
- )
- )
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_END,
- testConfig =
- TestConfig(
- isVisible = false,
- )
- )
-
- assertThat(values)
- .isEqualTo(
- listOf(
- // Initially, no button is visible so the indication area is not padded.
- false,
- // Once we add the first visible button, the indication area becomes padded.
- // This
- // continues to be true after we add the second visible button and even after we
- // make the first button not visible anymore.
- true,
- // Once both buttons are not visible, the indication area is, again, not padded.
- false,
+ val configKey =
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig = testConfig,
)
- )
- job.cancel()
- }
-
- @Test
- fun indicationAreaTranslationX() = runBlockingTest {
- val values = mutableListOf<Float>()
- val job = underTest.indicationAreaTranslationX.onEach(values::add).launchIn(this)
- repository.setClockPosition(100, 100)
- repository.setClockPosition(200, 100)
- repository.setClockPosition(200, 200)
- repository.setClockPosition(300, 100)
+ val latest = collectLastValue(underTest.startButton)
- assertThat(values).isEqualTo(listOf(0f, 100f, 200f, 300f))
- job.cancel()
- }
+ assertQuickAffordanceViewModel(
+ viewModel = latest(),
+ testConfig = testConfig,
+ configKey = configKey,
+ )
+ }
@Test
- fun indicationAreaTranslationY() = runBlockingTest {
- val values = mutableListOf<Float>()
- val job =
- underTest
- .indicationAreaTranslationY(DEFAULT_BURN_IN_OFFSET)
- .onEach(values::add)
- .launchIn(this)
-
- val expectedTranslationValues =
- listOf(
- -0f, // Negative 0 - apparently there's a difference in floating point arithmetic -
- // FML
- setDozeAmountAndCalculateExpectedTranslationY(0.1f),
- setDozeAmountAndCalculateExpectedTranslationY(0.2f),
- setDozeAmountAndCalculateExpectedTranslationY(0.5f),
- setDozeAmountAndCalculateExpectedTranslationY(1f),
+ fun `isClickable - true when alpha above threshold`() =
+ testScope.runTest {
+ repository.setKeyguardShowing(true)
+ val latest = collectLastValue(underTest.startButton)
+ repository.setBottomAreaAlpha(
+ min(1f, KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD + 0.1f),
)
- assertThat(values).isEqualTo(expectedTranslationValues)
- job.cancel()
- }
+ val testConfig =
+ TestConfig(
+ isVisible = true,
+ isClickable = true,
+ icon = mock(),
+ canShowWhileLocked = false,
+ intent = Intent("action"),
+ )
+ val configKey =
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig = testConfig,
+ )
- @Test
- fun `isClickable - true when alpha at threshold`() = runBlockingTest {
- repository.setKeyguardShowing(true)
- repository.setBottomAreaAlpha(
- KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD
- )
-
- val testConfig =
- TestConfig(
- isVisible = true,
- isClickable = true,
- icon = mock(),
- canShowWhileLocked = false,
- intent = Intent("action"),
- )
- val configKey =
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ assertQuickAffordanceViewModel(
+ viewModel = latest(),
testConfig = testConfig,
+ configKey = configKey,
)
-
- var latest: KeyguardQuickAffordanceViewModel? = null
- val job = underTest.startButton.onEach { latest = it }.launchIn(this)
- // The interactor has an onStart { emit(Hidden) } to cover for upstream configs that don't
- // produce an initial value. We yield to give the coroutine time to emit the first real
- // value from our config.
- yield()
-
- assertQuickAffordanceViewModel(
- viewModel = latest,
- testConfig = testConfig,
- configKey = configKey,
- )
- job.cancel()
- }
+ }
@Test
- fun `isClickable - true when alpha above threshold`() = runBlockingTest {
- repository.setKeyguardShowing(true)
- var latest: KeyguardQuickAffordanceViewModel? = null
- val job = underTest.startButton.onEach { latest = it }.launchIn(this)
- repository.setBottomAreaAlpha(
- min(1f, KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD + 0.1f),
- )
-
- val testConfig =
- TestConfig(
- isVisible = true,
- isClickable = true,
- icon = mock(),
- canShowWhileLocked = false,
- intent = Intent("action"),
- )
- val configKey =
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_START,
- testConfig = testConfig,
+ fun `isClickable - false when alpha below threshold`() =
+ testScope.runTest {
+ repository.setKeyguardShowing(true)
+ val latest = collectLastValue(underTest.startButton)
+ repository.setBottomAreaAlpha(
+ max(0f, KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD - 0.1f),
)
- assertQuickAffordanceViewModel(
- viewModel = latest,
- testConfig = testConfig,
- configKey = configKey,
- )
- job.cancel()
- }
+ val testConfig =
+ TestConfig(
+ isVisible = true,
+ isClickable = false,
+ icon = mock(),
+ canShowWhileLocked = false,
+ intent = Intent("action"),
+ )
+ val configKey =
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig = testConfig,
+ )
- @Test
- fun `isClickable - false when alpha below threshold`() = runBlockingTest {
- repository.setKeyguardShowing(true)
- var latest: KeyguardQuickAffordanceViewModel? = null
- val job = underTest.startButton.onEach { latest = it }.launchIn(this)
- repository.setBottomAreaAlpha(
- max(0f, KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD - 0.1f),
- )
-
- val testConfig =
- TestConfig(
- isVisible = true,
- isClickable = false,
- icon = mock(),
- canShowWhileLocked = false,
- intent = Intent("action"),
- )
- val configKey =
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ assertQuickAffordanceViewModel(
+ viewModel = latest(),
testConfig = testConfig,
+ configKey = configKey,
)
-
- assertQuickAffordanceViewModel(
- viewModel = latest,
- testConfig = testConfig,
- configKey = configKey,
- )
- job.cancel()
- }
+ }
@Test
- fun `isClickable - false when alpha at zero`() = runBlockingTest {
- repository.setKeyguardShowing(true)
- var latest: KeyguardQuickAffordanceViewModel? = null
- val job = underTest.startButton.onEach { latest = it }.launchIn(this)
- repository.setBottomAreaAlpha(0f)
-
- val testConfig =
- TestConfig(
- isVisible = true,
- isClickable = false,
- icon = mock(),
- canShowWhileLocked = false,
- intent = Intent("action"),
- )
- val configKey =
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ fun `isClickable - false when alpha at zero`() =
+ testScope.runTest {
+ repository.setKeyguardShowing(true)
+ val latest = collectLastValue(underTest.startButton)
+ repository.setBottomAreaAlpha(0f)
+
+ val testConfig =
+ TestConfig(
+ isVisible = true,
+ isClickable = false,
+ icon = mock(),
+ canShowWhileLocked = false,
+ intent = Intent("action"),
+ )
+ val configKey =
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig = testConfig,
+ )
+
+ assertQuickAffordanceViewModel(
+ viewModel = latest(),
testConfig = testConfig,
+ configKey = configKey,
)
+ }
- assertQuickAffordanceViewModel(
- viewModel = latest,
- testConfig = testConfig,
- configKey = configKey,
- )
- job.cancel()
- }
-
- private suspend fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float {
+ private fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float {
repository.setDozeAmount(dozeAmount)
return dozeAmount * (RETURNED_BURN_IN_OFFSET - DEFAULT_BURN_IN_OFFSET)
}
@@ -583,7 +615,6 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
when (testConfig.isActivated) {
true -> ActivationState.Active
false -> ActivationState.Inactive
- null -> ActivationState.NotSupported
}
)
} else {
@@ -636,6 +667,5 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
companion object {
private const val DEFAULT_BURN_IN_OFFSET = 5
private const val RETURNED_BURN_IN_OFFSET = 3
- private val IMMEDIATE = Dispatchers.Main.immediate
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
index e009e8651f2a..0e7bf8d9d465 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt
@@ -22,6 +22,7 @@ import com.android.systemui.dump.DumpManager
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogcatEchoTracker
+import com.android.systemui.temporarydisplay.TemporaryViewInfo
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
@@ -33,7 +34,7 @@ import org.mockito.Mockito.mock
class MediaTttLoggerTest : SysuiTestCase() {
private lateinit var buffer: LogBuffer
- private lateinit var logger: MediaTttLogger
+ private lateinit var logger: MediaTttLogger<TemporaryViewInfo>
@Before
fun setUp () {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
index cce3e369c0b8..561867f78e60 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
@@ -25,6 +25,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.temporarydisplay.TemporaryViewInfo
import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -40,7 +41,7 @@ class MediaTttUtilsTest : SysuiTestCase() {
private lateinit var appIconFromPackageName: Drawable
@Mock private lateinit var packageManager: PackageManager
@Mock private lateinit var applicationInfo: ApplicationInfo
- @Mock private lateinit var logger: MediaTttLogger
+ @Mock private lateinit var logger: MediaTttLogger<TemporaryViewInfo>
@Before
fun setUp() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
index 4aa982ed1609..bad3f0374a31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
@@ -27,13 +27,14 @@ import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.time.SystemClock
import com.android.systemui.util.view.ViewUtil
import com.android.systemui.util.wakelock.WakeLock
class FakeMediaTttChipControllerReceiver(
commandQueue: CommandQueue,
context: Context,
- logger: MediaTttLogger,
+ logger: MediaTttLogger<ChipReceiverInfo>,
windowManager: WindowManager,
mainExecutor: DelayableExecutor,
accessibilityManager: AccessibilityManager,
@@ -44,6 +45,7 @@ class FakeMediaTttChipControllerReceiver(
uiEventLogger: MediaTttReceiverUiEventLogger,
viewUtil: ViewUtil,
wakeLockBuilder: WakeLock.Builder,
+ systemClock: SystemClock,
) :
MediaTttChipControllerReceiver(
commandQueue,
@@ -59,6 +61,7 @@ class FakeMediaTttChipControllerReceiver(
uiEventLogger,
viewUtil,
wakeLockBuilder,
+ systemClock,
) {
override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
// Just bypass the animation in tests
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index 23f7cdb45026..ef0bfb7b6700 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -67,7 +67,7 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() {
@Mock
private lateinit var applicationInfo: ApplicationInfo
@Mock
- private lateinit var logger: MediaTttLogger
+ private lateinit var logger: MediaTttLogger<ChipReceiverInfo>
@Mock
private lateinit var accessibilityManager: AccessibilityManager
@Mock
@@ -128,6 +128,7 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() {
receiverUiEventLogger,
viewUtil,
fakeWakeLockBuilder,
+ fakeClock,
)
controllerReceiver.start()
@@ -155,6 +156,7 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() {
receiverUiEventLogger,
viewUtil,
fakeWakeLockBuilder,
+ fakeClock,
)
controllerReceiver.start()
@@ -193,6 +195,36 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() {
}
@Test
+ fun commandQueueCallback_transferToReceiverSucceeded_noChipShown() {
+ commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
+ StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+ routeInfo,
+ null,
+ null
+ )
+
+ verify(windowManager, never()).addView(any(), any())
+ assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
+ MediaTttReceiverUiEvents.MEDIA_TTT_RECEIVER_TRANSFER_TO_RECEIVER_SUCCEEDED.id
+ )
+ }
+
+ @Test
+ fun commandQueueCallback_transferToReceiverFailed_noChipShown() {
+ commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
+ StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_FAILED,
+ routeInfo,
+ null,
+ null
+ )
+
+ verify(windowManager, never()).addView(any(), any())
+ assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(
+ MediaTttReceiverUiEvents.MEDIA_TTT_RECEIVER_TRANSFER_TO_RECEIVER_FAILED.id
+ )
+ }
+
+ @Test
fun commandQueueCallback_closeThenFar_chipShownThenHidden() {
commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
@@ -214,6 +246,48 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() {
}
@Test
+ fun commandQueueCallback_closeThenSucceeded_chipShownThenHidden() {
+ commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
+ StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
+ routeInfo,
+ null,
+ null
+ )
+
+ commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
+ StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+ routeInfo,
+ null,
+ null
+ )
+
+ val viewCaptor = ArgumentCaptor.forClass(View::class.java)
+ verify(windowManager).addView(viewCaptor.capture(), any())
+ verify(windowManager).removeView(viewCaptor.value)
+ }
+
+ @Test
+ fun commandQueueCallback_closeThenFailed_chipShownThenHidden() {
+ commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
+ StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
+ routeInfo,
+ null,
+ null
+ )
+
+ commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
+ StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_TRANSFER_TO_RECEIVER_FAILED,
+ routeInfo,
+ null,
+ null
+ )
+
+ val viewCaptor = ArgumentCaptor.forClass(View::class.java)
+ verify(windowManager).addView(viewCaptor.capture(), any())
+ verify(windowManager).removeView(viewCaptor.value)
+ }
+
+ @Test
fun commandQueueCallback_closeThenFar_wakeLockAcquiredThenReleased() {
commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
index 311740e17310..b03a545f787f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
@@ -45,6 +45,7 @@ import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
+import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
import com.android.systemui.temporarydisplay.chipbar.ChipbarLogger
import com.android.systemui.temporarydisplay.chipbar.FakeChipbarCoordinator
import com.android.systemui.util.concurrency.FakeExecutor
@@ -83,7 +84,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
@Mock private lateinit var falsingManager: FalsingManager
@Mock private lateinit var falsingCollector: FalsingCollector
@Mock private lateinit var chipbarLogger: ChipbarLogger
- @Mock private lateinit var logger: MediaTttLogger
+ @Mock private lateinit var logger: MediaTttLogger<ChipbarInfo>
@Mock private lateinit var mediaTttFlags: MediaTttFlags
@Mock private lateinit var packageManager: PackageManager
@Mock private lateinit var powerManager: PowerManager
@@ -142,6 +143,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
viewUtil,
vibratorHelper,
fakeWakeLockBuilder,
+ fakeClock,
)
chipbarCoordinator.start()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
index 3ae842862366..b59005aa8dda 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
@@ -86,7 +86,8 @@ public class InternetDialogTest extends SysuiTestCase {
private View mDialogView;
private View mSubTitle;
private LinearLayout mEthernet;
- private LinearLayout mMobileDataToggle;
+ private LinearLayout mMobileDataLayout;
+ private Switch mMobileToggleSwitch;
private LinearLayout mWifiToggle;
private Switch mWifiToggleSwitch;
private TextView mWifiToggleSummary;
@@ -135,7 +136,8 @@ public class InternetDialogTest extends SysuiTestCase {
mDialogView = mInternetDialog.mDialogView;
mSubTitle = mDialogView.requireViewById(R.id.internet_dialog_subtitle);
mEthernet = mDialogView.requireViewById(R.id.ethernet_layout);
- mMobileDataToggle = mDialogView.requireViewById(R.id.mobile_network_layout);
+ mMobileDataLayout = mDialogView.requireViewById(R.id.mobile_network_layout);
+ mMobileToggleSwitch = mDialogView.requireViewById(R.id.mobile_toggle);
mWifiToggle = mDialogView.requireViewById(R.id.turn_on_wifi_layout);
mWifiToggleSwitch = mDialogView.requireViewById(R.id.wifi_toggle);
mWifiToggleSummary = mDialogView.requireViewById(R.id.wifi_toggle_summary);
@@ -236,7 +238,7 @@ public class InternetDialogTest extends SysuiTestCase {
mInternetDialog.updateDialog(true);
- assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.GONE);
}
@Test
@@ -248,7 +250,7 @@ public class InternetDialogTest extends SysuiTestCase {
mInternetDialog.updateDialog(true);
- assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.GONE);
// Carrier network should be visible if airplane mode ON and Wi-Fi is ON.
when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
@@ -257,7 +259,7 @@ public class InternetDialogTest extends SysuiTestCase {
mInternetDialog.updateDialog(true);
- assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.VISIBLE);
}
@Test
@@ -267,7 +269,7 @@ public class InternetDialogTest extends SysuiTestCase {
mInternetDialog.updateDialog(true);
- assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.GONE);
}
@Test
@@ -279,7 +281,7 @@ public class InternetDialogTest extends SysuiTestCase {
mInternetDialog.updateDialog(true);
- assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mAirplaneModeSummaryText.getVisibility()).isEqualTo(View.VISIBLE);
}
@@ -316,6 +318,30 @@ public class InternetDialogTest extends SysuiTestCase {
}
@Test
+ public void updateDialog_mobileDataIsEnabled_checkMobileDataSwitch() {
+ doReturn(true).when(mInternetDialogController).hasActiveSubId();
+ when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
+ when(mInternetDialogController.isMobileDataEnabled()).thenReturn(true);
+ mMobileToggleSwitch.setChecked(false);
+
+ mInternetDialog.updateDialog(true);
+
+ assertThat(mMobileToggleSwitch.isChecked()).isTrue();
+ }
+
+ @Test
+ public void updateDialog_mobileDataIsNotChanged_checkMobileDataSwitch() {
+ doReturn(true).when(mInternetDialogController).hasActiveSubId();
+ when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
+ when(mInternetDialogController.isMobileDataEnabled()).thenReturn(false);
+ mMobileToggleSwitch.setChecked(false);
+
+ mInternetDialog.updateDialog(true);
+
+ assertThat(mMobileToggleSwitch.isChecked()).isFalse();
+ }
+
+ @Test
public void updateDialog_wifiOnAndHasInternetWifi_showConnectedWifi() {
mInternetDialog.dismissDialog();
doReturn(true).when(mInternetDialogController).hasActiveSubId();
@@ -695,7 +721,7 @@ public class InternetDialogTest extends SysuiTestCase {
private void setNetworkVisible(boolean ethernetVisible, boolean mobileDataVisible,
boolean connectedWifiVisible) {
mEthernet.setVisibility(ethernetVisible ? View.VISIBLE : View.GONE);
- mMobileDataToggle.setVisibility(mobileDataVisible ? View.VISIBLE : View.GONE);
+ mMobileDataLayout.setVisibility(mobileDataVisible ? View.VISIBLE : View.GONE);
mConnectedWifi.setVisibility(connectedWifiVisible ? View.VISIBLE : View.GONE);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 0302dade0a8c..351274913323 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -1102,6 +1102,17 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
mStatusBarStateController.setState(KEYGUARD);
+ assertThat(mNotificationPanelViewController.isQsExpanded()).isEqualTo(false);
+ assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isEqualTo(false);
+ }
+
+ @Test
+ public void testLockedSplitShadeTransitioningToKeyguard_closesQS() {
+ enableSplitShade(true);
+ mStatusBarStateController.setState(SHADE_LOCKED);
+ mNotificationPanelViewController.setQsExpanded(true);
+
+ mStatusBarStateController.setState(KEYGUARD);
assertThat(mNotificationPanelViewController.isQsExpanded()).isEqualTo(false);
assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isEqualTo(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt
new file mode 100644
index 000000000000..33b94e39c019
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.logging
+
+import android.app.Notification
+import android.app.StatsManager
+import android.graphics.Bitmap
+import android.graphics.drawable.Icon
+import android.testing.AndroidTestingRunner
+import android.util.StatsEvent
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.shared.system.SysUiStatsLog
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class NotificationMemoryLoggerTest : SysuiTestCase() {
+
+ private val bgExecutor = FakeExecutor(FakeSystemClock())
+ private val immediate = Dispatchers.Main.immediate
+
+ @Mock private lateinit var statsManager: StatsManager
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun onInit_registersCallback() {
+ val logger = createLoggerWithNotifications(listOf())
+ logger.init()
+ verify(statsManager)
+ .setPullAtomCallback(SysUiStatsLog.NOTIFICATION_MEMORY_USE, null, bgExecutor, logger)
+ }
+
+ @Test
+ fun onPullAtom_wrongAtomId_returnsSkip() {
+ val logger = createLoggerWithNotifications(listOf())
+ val data: MutableList<StatsEvent> = mutableListOf()
+ assertThat(logger.onPullAtom(111, data)).isEqualTo(StatsManager.PULL_SKIP)
+ assertThat(data).isEmpty()
+ }
+
+ @Test
+ fun onPullAtom_emptyNotifications_returnsZeros() {
+ val logger = createLoggerWithNotifications(listOf())
+ val data: MutableList<StatsEvent> = mutableListOf()
+ assertThat(logger.onPullAtom(SysUiStatsLog.NOTIFICATION_MEMORY_USE, data))
+ .isEqualTo(StatsManager.PULL_SUCCESS)
+ assertThat(data).isEmpty()
+ }
+
+ @Test
+ fun onPullAtom_notificationPassed_populatesData() {
+ val icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888))
+ val notification =
+ Notification.Builder(context).setSmallIcon(icon).setContentTitle("title").build()
+ val logger = createLoggerWithNotifications(listOf(notification))
+ val data: MutableList<StatsEvent> = mutableListOf()
+
+ assertThat(logger.onPullAtom(SysUiStatsLog.NOTIFICATION_MEMORY_USE, data))
+ .isEqualTo(StatsManager.PULL_SUCCESS)
+ assertThat(data).hasSize(1)
+ }
+
+ @Test
+ fun onPullAtom_multipleNotificationsPassed_populatesData() {
+ val icon = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888))
+ val notification =
+ Notification.Builder(context).setSmallIcon(icon).setContentTitle("title").build()
+ val iconTwo = Icon.createWithBitmap(Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888))
+
+ val notificationTwo =
+ Notification.Builder(context)
+ .setStyle(Notification.BigTextStyle().bigText("text"))
+ .setSmallIcon(iconTwo)
+ .setContentTitle("titleTwo")
+ .build()
+ val logger = createLoggerWithNotifications(listOf(notification, notificationTwo))
+ val data: MutableList<StatsEvent> = mutableListOf()
+
+ assertThat(logger.onPullAtom(SysUiStatsLog.NOTIFICATION_MEMORY_USE, data))
+ .isEqualTo(StatsManager.PULL_SUCCESS)
+ assertThat(data).hasSize(2)
+ }
+
+ private fun createLoggerWithNotifications(
+ notifications: List<Notification>
+ ): NotificationMemoryLogger {
+ val pipeline: NotifPipeline = mock()
+ val notifications =
+ notifications.map { notification ->
+ NotificationEntryBuilder().setTag("test").setNotification(notification).build()
+ }
+ whenever(pipeline.allNotifs).thenReturn(notifications)
+ return NotificationMemoryLogger(pipeline, statsManager, immediate, bgExecutor)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt
index f69839b7087c..072a497f1a65 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt
@@ -23,6 +23,7 @@ import android.app.Person
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.drawable.Icon
+import android.stats.sysui.NotificationEnums
import android.testing.AndroidTestingRunner
import android.widget.RemoteViews
import androidx.test.filters.SmallTest
@@ -50,7 +51,27 @@ class NotificationMemoryMeterTest : SysuiTestCase() {
extras = 3316,
bigPicture = 0,
extender = 0,
- style = null,
+ style = NotificationEnums.STYLE_NONE,
+ styleIcon = 0,
+ hasCustomView = false,
+ )
+ }
+
+ @Test
+ fun currentNotificationMemoryUse_rankerGroupNotification() {
+ val notification = createBasicNotification().build()
+ val memoryUse =
+ NotificationMemoryMeter.notificationMemoryUse(
+ createNotificationEntry(createBasicNotification().setGroup("ranker_group").build())
+ )
+ assertNotificationObjectSizes(
+ memoryUse,
+ smallIcon = notification.smallIcon.bitmap.allocationByteCount,
+ largeIcon = notification.getLargeIcon().bitmap.allocationByteCount,
+ extras = 3316,
+ bigPicture = 0,
+ extender = 0,
+ style = NotificationEnums.STYLE_RANKER_GROUP,
styleIcon = 0,
hasCustomView = false,
)
@@ -69,7 +90,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() {
extras = 3316,
bigPicture = 0,
extender = 0,
- style = null,
+ style = NotificationEnums.STYLE_NONE,
styleIcon = 0,
hasCustomView = false,
)
@@ -92,7 +113,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() {
extras = 3384,
bigPicture = 0,
extender = 0,
- style = null,
+ style = NotificationEnums.STYLE_NONE,
styleIcon = 0,
hasCustomView = true,
)
@@ -112,7 +133,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() {
extras = 3212,
bigPicture = 0,
extender = 0,
- style = null,
+ style = NotificationEnums.STYLE_NONE,
styleIcon = 0,
hasCustomView = false,
)
@@ -141,7 +162,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() {
extras = 4092,
bigPicture = bigPicture.bitmap.allocationByteCount,
extender = 0,
- style = "BigPictureStyle",
+ style = NotificationEnums.STYLE_BIG_PICTURE,
styleIcon = bigPictureIcon.bitmap.allocationByteCount,
hasCustomView = false,
)
@@ -167,7 +188,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() {
extras = 4084,
bigPicture = 0,
extender = 0,
- style = "CallStyle",
+ style = NotificationEnums.STYLE_CALL,
styleIcon = personIcon.bitmap.allocationByteCount,
hasCustomView = false,
)
@@ -203,7 +224,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() {
extras = 5024,
bigPicture = 0,
extender = 0,
- style = "MessagingStyle",
+ style = NotificationEnums.STYLE_MESSAGING,
styleIcon =
personIcon.bitmap.allocationByteCount +
historicPersonIcon.bitmap.allocationByteCount,
@@ -225,7 +246,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() {
extras = 3612,
bigPicture = 0,
extender = 556656,
- style = null,
+ style = NotificationEnums.STYLE_NONE,
styleIcon = 0,
hasCustomView = false,
)
@@ -246,7 +267,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() {
extras = 3820,
bigPicture = 0,
extender = 388 + wearBackground.allocationByteCount,
- style = null,
+ style = NotificationEnums.STYLE_NONE,
styleIcon = 0,
hasCustomView = false,
)
@@ -272,7 +293,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() {
extras: Int,
bigPicture: Int,
extender: Int,
- style: String?,
+ style: Int,
styleIcon: Int,
hasCustomView: Boolean,
) {
@@ -282,11 +303,7 @@ class NotificationMemoryMeterTest : SysuiTestCase() {
assertThat(memoryUse.objectUsage.smallIcon).isEqualTo(smallIcon)
assertThat(memoryUse.objectUsage.largeIcon).isEqualTo(largeIcon)
assertThat(memoryUse.objectUsage.bigPicture).isEqualTo(bigPicture)
- if (style == null) {
- assertThat(memoryUse.objectUsage.style).isNull()
- } else {
- assertThat(memoryUse.objectUsage.style).isEqualTo(style)
- }
+ assertThat(memoryUse.objectUsage.style).isEqualTo(style)
assertThat(memoryUse.objectUsage.styleIcon).isEqualTo(styleIcon)
assertThat(memoryUse.objectUsage.hasCustomView).isEqualTo(hasCustomView)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt
index 3a16fb33388b..a0f50486ffff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt
@@ -8,6 +8,7 @@ import android.testing.TestableLooper
import android.widget.RemoteViews
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder
import com.android.systemui.statusbar.notification.row.NotificationTestHelper
import com.android.systemui.tests.R
import com.google.common.truth.Truth.assertThat
@@ -39,16 +40,84 @@ class NotificationMemoryViewWalkerTest : SysuiTestCase() {
fun testViewWalker_plainNotification() {
val row = testHelper.createRow()
val result = NotificationMemoryViewWalker.getViewUsage(row)
- assertThat(result).hasSize(5)
- assertThat(result).contains(NotificationViewUsage(ViewType.PUBLIC_VIEW, 0, 0, 0, 0, 0, 0))
- assertThat(result)
- .contains(NotificationViewUsage(ViewType.PRIVATE_HEADS_UP_VIEW, 0, 0, 0, 0, 0, 0))
+ assertThat(result).hasSize(3)
assertThat(result)
.contains(NotificationViewUsage(ViewType.PRIVATE_EXPANDED_VIEW, 0, 0, 0, 0, 0, 0))
assertThat(result)
.contains(NotificationViewUsage(ViewType.PRIVATE_CONTRACTED_VIEW, 0, 0, 0, 0, 0, 0))
+ assertThat(result).contains(NotificationViewUsage(ViewType.TOTAL, 0, 0, 0, 0, 0, 0))
+ }
+
+ @Test
+ fun testViewWalker_plainNotification_withPublicView() {
+ val icon = Icon.createWithBitmap(Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888))
+ val publicIcon = Icon.createWithBitmap(Bitmap.createBitmap(40, 40, Bitmap.Config.ARGB_8888))
+ testHelper.setDefaultInflationFlags(NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL)
+ val row =
+ testHelper.createRow(
+ Notification.Builder(mContext)
+ .setContentText("Test")
+ .setContentTitle("title")
+ .setSmallIcon(icon)
+ .setPublicVersion(
+ Notification.Builder(mContext)
+ .setContentText("Public Test")
+ .setContentTitle("title")
+ .setSmallIcon(publicIcon)
+ .build()
+ )
+ .build()
+ )
+ val result = NotificationMemoryViewWalker.getViewUsage(row)
+ assertThat(result).hasSize(4)
assertThat(result)
- .contains(NotificationViewUsage(ViewType.PRIVATE_HEADS_UP_VIEW, 0, 0, 0, 0, 0, 0))
+ .contains(
+ NotificationViewUsage(
+ ViewType.PRIVATE_EXPANDED_VIEW,
+ icon.bitmap.allocationByteCount,
+ 0,
+ 0,
+ 0,
+ 0,
+ icon.bitmap.allocationByteCount
+ )
+ )
+ assertThat(result)
+ .contains(
+ NotificationViewUsage(
+ ViewType.PRIVATE_CONTRACTED_VIEW,
+ icon.bitmap.allocationByteCount,
+ 0,
+ 0,
+ 0,
+ 0,
+ icon.bitmap.allocationByteCount
+ )
+ )
+ assertThat(result)
+ .contains(
+ NotificationViewUsage(
+ ViewType.PUBLIC_VIEW,
+ publicIcon.bitmap.allocationByteCount,
+ 0,
+ 0,
+ 0,
+ 0,
+ publicIcon.bitmap.allocationByteCount
+ )
+ )
+ assertThat(result)
+ .contains(
+ NotificationViewUsage(
+ ViewType.TOTAL,
+ icon.bitmap.allocationByteCount + publicIcon.bitmap.allocationByteCount,
+ 0,
+ 0,
+ 0,
+ 0,
+ icon.bitmap.allocationByteCount + publicIcon.bitmap.allocationByteCount
+ )
+ )
}
@Test
@@ -67,7 +136,7 @@ class NotificationMemoryViewWalkerTest : SysuiTestCase() {
.build()
)
val result = NotificationMemoryViewWalker.getViewUsage(row)
- assertThat(result).hasSize(5)
+ assertThat(result).hasSize(3)
assertThat(result)
.contains(
NotificationViewUsage(
@@ -95,8 +164,20 @@ class NotificationMemoryViewWalkerTest : SysuiTestCase() {
icon.bitmap.allocationByteCount + largeIcon.bitmap.allocationByteCount
)
)
- // Due to deduplication, this should all be 0.
- assertThat(result).contains(NotificationViewUsage(ViewType.PUBLIC_VIEW, 0, 0, 0, 0, 0, 0))
+ assertThat(result)
+ .contains(
+ NotificationViewUsage(
+ ViewType.TOTAL,
+ icon.bitmap.allocationByteCount,
+ largeIcon.bitmap.allocationByteCount,
+ 0,
+ bigPicture.allocationByteCount,
+ 0,
+ bigPicture.allocationByteCount +
+ icon.bitmap.allocationByteCount +
+ largeIcon.bitmap.allocationByteCount
+ )
+ )
}
@Test
@@ -117,7 +198,7 @@ class NotificationMemoryViewWalkerTest : SysuiTestCase() {
.build()
)
val result = NotificationMemoryViewWalker.getViewUsage(row)
- assertThat(result).hasSize(5)
+ assertThat(result).hasSize(3)
assertThat(result)
.contains(
NotificationViewUsage(
@@ -142,7 +223,17 @@ class NotificationMemoryViewWalkerTest : SysuiTestCase() {
bitmap.allocationByteCount + icon.bitmap.allocationByteCount
)
)
- // Due to deduplication, this should all be 0.
- assertThat(result).contains(NotificationViewUsage(ViewType.PUBLIC_VIEW, 0, 0, 0, 0, 0, 0))
+ assertThat(result)
+ .contains(
+ NotificationViewUsage(
+ ViewType.TOTAL,
+ icon.bitmap.allocationByteCount,
+ 0,
+ 0,
+ 0,
+ bitmap.allocationByteCount,
+ bitmap.allocationByteCount + icon.bitmap.allocationByteCount
+ )
+ )
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 4d9db8c28e07..58325697a408 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -518,7 +518,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
val childHunView = createHunViewMock(
isShadeOpen = true,
fullyVisible = false,
- headerVisibleAmount = 1f
+ headerVisibleAmount = 1f,
)
val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
algorithmState.visibleChildren.add(childHunView)
@@ -526,7 +526,6 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
// When: updateChildZValue() is called for the top HUN
stackScrollAlgorithm.updateChildZValue(
/* i= */ 0,
- /* childrenOnTop= */ 0.0f,
/* StackScrollAlgorithmState= */ algorithmState,
/* ambientState= */ ambientState,
/* shouldElevateHun= */ true
@@ -546,7 +545,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
val childHunView = createHunViewMock(
isShadeOpen = true,
fullyVisible = false,
- headerVisibleAmount = 1f
+ headerVisibleAmount = 1f,
)
// Use half of the HUN's height as overlap
childHunView.viewState.yTranslation = (childHunView.viewState.height + 1 shr 1).toFloat()
@@ -556,7 +555,6 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
// When: updateChildZValue() is called for the top HUN
stackScrollAlgorithm.updateChildZValue(
/* i= */ 0,
- /* childrenOnTop= */ 0.0f,
/* StackScrollAlgorithmState= */ algorithmState,
/* ambientState= */ ambientState,
/* shouldElevateHun= */ true
@@ -580,7 +578,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
val childHunView = createHunViewMock(
isShadeOpen = true,
fullyVisible = true,
- headerVisibleAmount = 1f
+ headerVisibleAmount = 1f,
)
// HUN doesn't overlap with QQS Panel
childHunView.viewState.yTranslation = ambientState.topPadding +
@@ -591,7 +589,6 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
// When: updateChildZValue() is called for the top HUN
stackScrollAlgorithm.updateChildZValue(
/* i= */ 0,
- /* childrenOnTop= */ 0.0f,
/* StackScrollAlgorithmState= */ algorithmState,
/* ambientState= */ ambientState,
/* shouldElevateHun= */ true
@@ -611,7 +608,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
val childHunView = createHunViewMock(
isShadeOpen = false,
fullyVisible = false,
- headerVisibleAmount = 0f
+ headerVisibleAmount = 0f,
)
childHunView.viewState.yTranslation = 0f
// Shade is closed, thus childHunView's headerVisibleAmount is 0
@@ -622,7 +619,6 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
// When: updateChildZValue() is called for the top HUN
stackScrollAlgorithm.updateChildZValue(
/* i= */ 0,
- /* childrenOnTop= */ 0.0f,
/* StackScrollAlgorithmState= */ algorithmState,
/* ambientState= */ ambientState,
/* shouldElevateHun= */ true
@@ -642,7 +638,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
val childHunView = createHunViewMock(
isShadeOpen = false,
fullyVisible = false,
- headerVisibleAmount = 0.5f
+ headerVisibleAmount = 0.5f,
)
childHunView.viewState.yTranslation = 0f
// Shade is being opened, thus childHunView's headerVisibleAmount is between 0 and 1
@@ -654,7 +650,6 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
// When: updateChildZValue() is called for the top HUN
stackScrollAlgorithm.updateChildZValue(
/* i= */ 0,
- /* childrenOnTop= */ 0.0f,
/* StackScrollAlgorithmState= */ algorithmState,
/* ambientState= */ ambientState,
/* shouldElevateHun= */ true
@@ -669,7 +664,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
private fun createHunViewMock(
isShadeOpen: Boolean,
fullyVisible: Boolean,
- headerVisibleAmount: Float
+ headerVisibleAmount: Float,
) =
mock<ExpandableNotificationRow>().apply {
val childViewStateMock = createHunChildViewState(isShadeOpen, fullyVisible)
@@ -680,7 +675,10 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
}
- private fun createHunChildViewState(isShadeOpen: Boolean, fullyVisible: Boolean) =
+ private fun createHunChildViewState(
+ isShadeOpen: Boolean,
+ fullyVisible: Boolean,
+ ) =
ExpandableViewState().apply {
// Mock the HUN's height with ambientState.topPadding +
// ambientState.stackTranslation
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
index 09f0d4a10410..82153d5610a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
@@ -35,6 +35,7 @@ import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.time.SystemClock
import com.android.systemui.util.wakelock.WakeLock
import com.android.systemui.util.wakelock.WakeLockFake
import com.google.common.truth.Truth.assertThat
@@ -59,7 +60,7 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
private lateinit var fakeWakeLock: WakeLockFake
@Mock
- private lateinit var logger: TemporaryViewLogger
+ private lateinit var logger: TemporaryViewLogger<ViewInfo>
@Mock
private lateinit var accessibilityManager: AccessibilityManager
@Mock
@@ -74,7 +75,7 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any()))
- .thenReturn(TIMEOUT_MS.toInt())
+ .thenAnswer { it.arguments[0] }
fakeClock = FakeSystemClock()
fakeExecutor = FakeExecutor(fakeClock)
@@ -84,14 +85,15 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
fakeWakeLockBuilder.setWakeLock(fakeWakeLock)
underTest = TestController(
- context,
- logger,
- windowManager,
- fakeExecutor,
- accessibilityManager,
- configurationController,
- powerManager,
- fakeWakeLockBuilder,
+ context,
+ logger,
+ windowManager,
+ fakeExecutor,
+ accessibilityManager,
+ configurationController,
+ powerManager,
+ fakeWakeLockBuilder,
+ fakeClock,
)
underTest.start()
}
@@ -112,14 +114,14 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
@Test
fun displayView_logged() {
- underTest.displayView(
- ViewInfo(
- name = "name",
- windowTitle = "Fake Window Title",
- )
+ val info = ViewInfo(
+ name = "name",
+ windowTitle = "Fake Window Title",
)
- verify(logger).logViewAddition("id", "Fake Window Title")
+ underTest.displayView(info)
+
+ verify(logger).logViewAddition(info)
}
@Test
@@ -168,10 +170,11 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
}
@Test
- fun displayView_twiceWithDifferentWindowTitles_oldViewRemovedNewViewAdded() {
+ fun displayView_twiceWithDifferentIds_oldViewRemovedNewViewAdded() {
underTest.displayView(
ViewInfo(
name = "name",
+ id = "First",
windowTitle = "First Fake Window Title",
)
)
@@ -179,6 +182,7 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
underTest.displayView(
ViewInfo(
name = "name",
+ id = "Second",
windowTitle = "Second Fake Window Title",
)
)
@@ -263,19 +267,69 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
}
@Test
+ fun viewUpdatedWithNewOnViewTimeoutRunnable_newRunnableUsed() {
+ var runnable1Run = false
+ underTest.displayView(ViewInfo(name = "name", id = "id1", windowTitle = "1")) {
+ runnable1Run = true
+ }
+
+ var runnable2Run = false
+ underTest.displayView(ViewInfo(name = "name", id = "id1", windowTitle = "1")) {
+ runnable2Run = true
+ }
+
+ fakeClock.advanceTime(TIMEOUT_MS + 1)
+
+ assertThat(runnable1Run).isFalse()
+ assertThat(runnable2Run).isTrue()
+ }
+
+ @Test
+ fun multipleViewsWithDifferentIds_moreRecentReplacesOlder() {
+ underTest.displayView(
+ ViewInfo(
+ name = "name",
+ windowTitle = "First Fake Window Title",
+ id = "id1"
+ )
+ )
+
+ underTest.displayView(
+ ViewInfo(
+ name = "name",
+ windowTitle = "Second Fake Window Title",
+ id = "id2"
+ )
+ )
+
+ val viewCaptor = argumentCaptor<View>()
+ val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+
+ verify(windowManager, times(2)).addView(capture(viewCaptor), capture(windowParamsCaptor))
+
+ assertThat(windowParamsCaptor.allValues[0].title).isEqualTo("First Fake Window Title")
+ assertThat(windowParamsCaptor.allValues[1].title).isEqualTo("Second Fake Window Title")
+ verify(windowManager).removeView(viewCaptor.allValues[0])
+ verify(configurationController, never()).removeCallback(any())
+ }
+
+ @Test
fun multipleViewsWithDifferentIds_recentActiveViewIsDisplayed() {
underTest.displayView(ViewInfo("First name", id = "id1"))
verify(windowManager).addView(any(), any())
-
reset(windowManager)
+
underTest.displayView(ViewInfo("Second name", id = "id2"))
- underTest.removeView("id2", "test reason")
verify(windowManager).removeView(any())
+ verify(windowManager).addView(any(), any())
+ reset(windowManager)
- fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1)
+ underTest.removeView("id2", "test reason")
+ verify(windowManager).removeView(any())
+ verify(windowManager).addView(any(), any())
assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1")
assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("First name")
@@ -284,6 +338,7 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
verify(windowManager).removeView(any())
assertThat(underTest.activeViews.size).isEqualTo(0)
+ verify(configurationController).removeCallback(any())
}
@Test
@@ -291,19 +346,28 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
underTest.displayView(ViewInfo("First name", id = "id1"))
verify(windowManager).addView(any(), any())
-
reset(windowManager)
+
underTest.displayView(ViewInfo("Second name", id = "id2"))
+
+ verify(windowManager).removeView(any())
+ verify(windowManager).addView(any(), any())
+ reset(windowManager)
+
+ // WHEN an old view is removed
underTest.removeView("id1", "test reason")
+ // THEN we don't update anything
verify(windowManager, never()).removeView(any())
assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id2")
assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("Second name")
+ verify(configurationController, never()).removeCallback(any())
fakeClock.advanceTime(TIMEOUT_MS + 1)
verify(windowManager).removeView(any())
assertThat(underTest.activeViews.size).isEqualTo(0)
+ verify(configurationController).removeCallback(any())
}
@Test
@@ -312,33 +376,31 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
underTest.displayView(ViewInfo("Second name", id = "id2"))
underTest.displayView(ViewInfo("Third name", id = "id3"))
- verify(windowManager).addView(any(), any())
+ verify(windowManager, times(3)).addView(any(), any())
+ verify(windowManager, times(2)).removeView(any())
reset(windowManager)
underTest.removeView("id3", "test reason")
verify(windowManager).removeView(any())
-
- fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1)
-
assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id2")
assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("Second name")
+ verify(configurationController, never()).removeCallback(any())
reset(windowManager)
underTest.removeView("id2", "test reason")
verify(windowManager).removeView(any())
-
- fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1)
-
assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1")
assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("First name")
+ verify(configurationController, never()).removeCallback(any())
reset(windowManager)
fakeClock.advanceTime(TIMEOUT_MS + 1)
verify(windowManager).removeView(any())
assertThat(underTest.activeViews.size).isEqualTo(0)
+ verify(configurationController).removeCallback(any())
}
@Test
@@ -347,18 +409,21 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
underTest.displayView(ViewInfo("New name", id = "id1"))
verify(windowManager).addView(any(), any())
-
reset(windowManager)
+
underTest.displayView(ViewInfo("Second name", id = "id2"))
- underTest.removeView("id2", "test reason")
verify(windowManager).removeView(any())
+ verify(windowManager).addView(any(), any())
+ reset(windowManager)
- fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1)
+ underTest.removeView("id2", "test reason")
+ verify(windowManager).removeView(any())
+ verify(windowManager).addView(any(), any())
assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1")
assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("New name")
- assertThat(underTest.activeViews[0].second.name).isEqualTo("New name")
+ assertThat(underTest.activeViews[0].info.name).isEqualTo("New name")
reset(windowManager)
fakeClock.advanceTime(TIMEOUT_MS + 1)
@@ -368,19 +433,523 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
}
@Test
- fun multipleViewsWithDifferentIds_viewsTimeouts_noViewLeftToDisplay() {
- underTest.displayView(ViewInfo("First name", id = "id1"))
- fakeClock.advanceTime(TIMEOUT_MS / 3)
- underTest.displayView(ViewInfo("Second name", id = "id2"))
- fakeClock.advanceTime(TIMEOUT_MS / 3)
- underTest.displayView(ViewInfo("Third name", id = "id3"))
+ fun multipleViews_mostRecentViewRemoved_otherViewsTimedOutAndNotDisplayed() {
+ underTest.displayView(ViewInfo("First name", id = "id1", timeoutMs = 4000))
+ fakeClock.advanceTime(1000)
+ underTest.displayView(ViewInfo("Second name", id = "id2", timeoutMs = 4000))
+ fakeClock.advanceTime(1000)
+ underTest.displayView(ViewInfo("Third name", id = "id3", timeoutMs = 20000))
reset(windowManager)
- fakeClock.advanceTime(TIMEOUT_MS + 1)
+ fakeClock.advanceTime(20000 + 1)
verify(windowManager).removeView(any())
verify(windowManager, never()).addView(any(), any())
assertThat(underTest.activeViews.size).isEqualTo(0)
+ verify(configurationController).removeCallback(any())
+ }
+
+ @Test
+ fun multipleViews_mostRecentViewRemoved_viewWithShortTimeLeftNotDisplayed() {
+ underTest.displayView(ViewInfo("First name", id = "id1", timeoutMs = 4000))
+ fakeClock.advanceTime(1000)
+ underTest.displayView(ViewInfo("Second name", id = "id2", timeoutMs = 2500))
+
+ reset(windowManager)
+ fakeClock.advanceTime(2500 + 1)
+ // At this point, 3501ms have passed, so id1 only has 499ms left which is not enough.
+ // So, it shouldn't be displayed.
+
+ verify(windowManager, never()).addView(any(), any())
+ assertThat(underTest.activeViews.size).isEqualTo(0)
+ verify(configurationController).removeCallback(any())
+ }
+
+ @Test
+ fun lowerThenHigherPriority_higherReplacesLower() {
+ underTest.displayView(
+ ViewInfo(
+ name = "normal",
+ windowTitle = "Normal Window Title",
+ id = "normal",
+ priority = ViewPriority.NORMAL,
+ )
+ )
+
+ val viewCaptor = argumentCaptor<View>()
+ val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+ verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title")
+ reset(windowManager)
+
+ underTest.displayView(
+ ViewInfo(
+ name = "critical",
+ windowTitle = "Critical Window Title",
+ id = "critical",
+ priority = ViewPriority.CRITICAL,
+ )
+ )
+
+ verify(windowManager).removeView(viewCaptor.value)
+ verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title")
+ verify(configurationController, never()).removeCallback(any())
+ }
+
+ @Test
+ fun lowerThenHigherPriority_lowerPriorityRedisplayed() {
+ underTest.displayView(
+ ViewInfo(
+ name = "normal",
+ windowTitle = "Normal Window Title",
+ id = "normal",
+ priority = ViewPriority.NORMAL,
+ timeoutMs = 10000
+ )
+ )
+
+ underTest.displayView(
+ ViewInfo(
+ name = "critical",
+ windowTitle = "Critical Window Title",
+ id = "critical",
+ priority = ViewPriority.CRITICAL,
+ timeoutMs = 2000
+ )
+ )
+
+ val viewCaptor = argumentCaptor<View>()
+ val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+ verify(windowManager, times(2)).addView(capture(viewCaptor), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.allValues[0].title).isEqualTo("Normal Window Title")
+ assertThat(windowParamsCaptor.allValues[1].title).isEqualTo("Critical Window Title")
+ verify(windowManager).removeView(viewCaptor.allValues[0])
+
+ reset(windowManager)
+
+ // WHEN the critical's timeout has expired
+ fakeClock.advanceTime(2000 + 1)
+
+ // THEN the normal view is re-displayed
+ verify(windowManager).removeView(viewCaptor.allValues[1])
+ verify(windowManager).addView(any(), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title")
+ verify(configurationController, never()).removeCallback(any())
+ }
+
+ @Test
+ fun lowerThenHigherPriority_lowerPriorityNotRedisplayedBecauseTimedOut() {
+ underTest.displayView(
+ ViewInfo(
+ name = "normal",
+ windowTitle = "Normal Window Title",
+ id = "normal",
+ priority = ViewPriority.NORMAL,
+ timeoutMs = 1000
+ )
+ )
+
+ underTest.displayView(
+ ViewInfo(
+ name = "critical",
+ windowTitle = "Critical Window Title",
+ id = "critical",
+ priority = ViewPriority.CRITICAL,
+ timeoutMs = 2000
+ )
+ )
+ reset(windowManager)
+
+ // WHEN the critical's timeout has expired
+ fakeClock.advanceTime(2000 + 1)
+
+ // THEN the normal view is not re-displayed since it already timed out
+ verify(windowManager).removeView(any())
+ verify(windowManager, never()).addView(any(), any())
+ assertThat(underTest.activeViews).isEmpty()
+ verify(configurationController).removeCallback(any())
+ }
+
+ @Test
+ fun higherThenLowerPriority_higherStaysDisplayed() {
+ underTest.displayView(
+ ViewInfo(
+ name = "critical",
+ windowTitle = "Critical Window Title",
+ id = "critical",
+ priority = ViewPriority.CRITICAL,
+ )
+ )
+
+ val viewCaptor = argumentCaptor<View>()
+ val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+ verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title")
+ reset(windowManager)
+
+ underTest.displayView(
+ ViewInfo(
+ name = "normal",
+ windowTitle = "Normal Window Title",
+ id = "normal",
+ priority = ViewPriority.NORMAL,
+ )
+ )
+
+ verify(windowManager, never()).removeView(viewCaptor.value)
+ verify(windowManager, never()).addView(any(), any())
+ assertThat(underTest.activeViews.size).isEqualTo(2)
+ verify(configurationController, never()).removeCallback(any())
+ }
+
+ @Test
+ fun higherThenLowerPriority_lowerEventuallyDisplayed() {
+ underTest.displayView(
+ ViewInfo(
+ name = "critical",
+ windowTitle = "Critical Window Title",
+ id = "critical",
+ priority = ViewPriority.CRITICAL,
+ timeoutMs = 3000,
+ )
+ )
+
+ val viewCaptor = argumentCaptor<View>()
+ val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+ verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title")
+ reset(windowManager)
+
+ underTest.displayView(
+ ViewInfo(
+ name = "normal",
+ windowTitle = "Normal Window Title",
+ id = "normal",
+ priority = ViewPriority.NORMAL,
+ timeoutMs = 5000,
+ )
+ )
+
+ verify(windowManager, never()).removeView(viewCaptor.value)
+ verify(windowManager, never()).addView(any(), any())
+ assertThat(underTest.activeViews.size).isEqualTo(2)
+
+ // WHEN the first critical view has timed out
+ fakeClock.advanceTime(3000 + 1)
+
+ // THEN the second normal view is displayed
+ verify(windowManager).removeView(viewCaptor.value)
+ verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title")
+ assertThat(underTest.activeViews.size).isEqualTo(1)
+ verify(configurationController, never()).removeCallback(any())
+ }
+
+ @Test
+ fun higherThenLowerPriority_lowerNotDisplayedBecauseTimedOut() {
+ underTest.displayView(
+ ViewInfo(
+ name = "critical",
+ windowTitle = "Critical Window Title",
+ id = "critical",
+ priority = ViewPriority.CRITICAL,
+ timeoutMs = 3000,
+ )
+ )
+
+ val viewCaptor = argumentCaptor<View>()
+ val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+ verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title")
+ reset(windowManager)
+
+ underTest.displayView(
+ ViewInfo(
+ name = "normal",
+ windowTitle = "Normal Window Title",
+ id = "normal",
+ priority = ViewPriority.NORMAL,
+ timeoutMs = 200,
+ )
+ )
+
+ verify(windowManager, never()).removeView(viewCaptor.value)
+ verify(windowManager, never()).addView(any(), any())
+ assertThat(underTest.activeViews.size).isEqualTo(2)
+ reset(windowManager)
+
+ // WHEN the first critical view has timed out
+ fakeClock.advanceTime(3000 + 1)
+
+ // THEN the second normal view is not displayed because it's already timed out
+ verify(windowManager).removeView(viewCaptor.value)
+ verify(windowManager, never()).addView(any(), any())
+ assertThat(underTest.activeViews).isEmpty()
+ verify(configurationController).removeCallback(any())
+ }
+
+ @Test
+ fun criticalThenNewCritical_newCriticalDisplayed() {
+ underTest.displayView(
+ ViewInfo(
+ name = "critical 1",
+ windowTitle = "Critical Window Title 1",
+ id = "critical1",
+ priority = ViewPriority.CRITICAL,
+ )
+ )
+
+ val viewCaptor = argumentCaptor<View>()
+ val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+ verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title 1")
+ reset(windowManager)
+
+ underTest.displayView(
+ ViewInfo(
+ name = "critical 2",
+ windowTitle = "Critical Window Title 2",
+ id = "critical2",
+ priority = ViewPriority.CRITICAL,
+ )
+ )
+
+ verify(windowManager).removeView(viewCaptor.value)
+ verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title 2")
+ assertThat(underTest.activeViews.size).isEqualTo(2)
+ verify(configurationController, never()).removeCallback(any())
+ }
+
+ @Test
+ fun normalThenNewNormal_newNormalDisplayed() {
+ underTest.displayView(
+ ViewInfo(
+ name = "normal 1",
+ windowTitle = "Normal Window Title 1",
+ id = "normal1",
+ priority = ViewPriority.NORMAL,
+ )
+ )
+
+ val viewCaptor = argumentCaptor<View>()
+ val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+ verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title 1")
+ reset(windowManager)
+
+ underTest.displayView(
+ ViewInfo(
+ name = "normal 2",
+ windowTitle = "Normal Window Title 2",
+ id = "normal2",
+ priority = ViewPriority.NORMAL,
+ )
+ )
+
+ verify(windowManager).removeView(viewCaptor.value)
+ verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title 2")
+ assertThat(underTest.activeViews.size).isEqualTo(2)
+ verify(configurationController, never()).removeCallback(any())
+ }
+
+ @Test
+ fun lowerPriorityViewUpdatedWhileHigherPriorityDisplayed_eventuallyDisplaysUpdated() {
+ // First, display a lower priority view
+ underTest.displayView(
+ ViewInfo(
+ name = "normal",
+ windowTitle = "Normal Window Title",
+ id = "normal",
+ priority = ViewPriority.NORMAL,
+ // At the end of the test, we'll verify that this information isn't re-displayed.
+ // Use a super long timeout so that, when we verify it wasn't re-displayed, we know
+ // that it wasn't because the view just timed out.
+ timeoutMs = 100000,
+ )
+ )
+
+ val viewCaptor = argumentCaptor<View>()
+ val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+ verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title")
+ reset(windowManager)
+
+ // Then, display a higher priority view
+ fakeClock.advanceTime(1000)
+ underTest.displayView(
+ ViewInfo(
+ name = "critical",
+ windowTitle = "Critical Window Title",
+ id = "critical",
+ priority = ViewPriority.CRITICAL,
+ timeoutMs = 3000,
+ )
+ )
+
+ verify(windowManager).removeView(viewCaptor.value)
+ verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value.title).isEqualTo("Critical Window Title")
+ assertThat(underTest.activeViews.size).isEqualTo(2)
+ reset(windowManager)
+
+ // While the higher priority view is displayed, update the lower priority view with new
+ // information
+ fakeClock.advanceTime(1000)
+ val updatedViewInfo = ViewInfo(
+ name = "normal with update",
+ windowTitle = "Normal Window Title",
+ id = "normal",
+ priority = ViewPriority.NORMAL,
+ timeoutMs = 4000,
+ )
+ underTest.displayView(updatedViewInfo)
+
+ verify(windowManager, never()).removeView(viewCaptor.value)
+ verify(windowManager, never()).addView(any(), any())
+ assertThat(underTest.activeViews.size).isEqualTo(2)
+ reset(windowManager)
+
+ // WHEN the higher priority view times out
+ fakeClock.advanceTime(2001)
+
+ // THEN the higher priority view disappears and the lower priority view *with the updated
+ // information* gets displayed.
+ verify(windowManager).removeView(viewCaptor.value)
+ verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value.title).isEqualTo("Normal Window Title")
+ assertThat(underTest.activeViews.size).isEqualTo(1)
+ assertThat(underTest.mostRecentViewInfo).isEqualTo(updatedViewInfo)
+ reset(windowManager)
+
+ // WHEN the updated view times out
+ fakeClock.advanceTime(2001)
+
+ // THEN the old information is never displayed
+ verify(windowManager).removeView(viewCaptor.value)
+ verify(windowManager, never()).addView(any(), any())
+ assertThat(underTest.activeViews.size).isEqualTo(0)
+ }
+
+ @Test
+ fun oldViewUpdatedWhileNewViewDisplayed_eventuallyDisplaysUpdated() {
+ // First, display id1 view
+ underTest.displayView(
+ ViewInfo(
+ name = "name 1",
+ windowTitle = "Name 1 Title",
+ id = "id1",
+ priority = ViewPriority.NORMAL,
+ // At the end of the test, we'll verify that this information isn't re-displayed.
+ // Use a super long timeout so that, when we verify it wasn't re-displayed, we know
+ // that it wasn't because the view just timed out.
+ timeoutMs = 100000,
+ )
+ )
+
+ val viewCaptor = argumentCaptor<View>()
+ val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+ verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value.title).isEqualTo("Name 1 Title")
+ reset(windowManager)
+
+ // Then, display a new id2 view
+ fakeClock.advanceTime(1000)
+ underTest.displayView(
+ ViewInfo(
+ name = "name 2",
+ windowTitle = "Name 2 Title",
+ id = "id2",
+ priority = ViewPriority.NORMAL,
+ timeoutMs = 3000,
+ )
+ )
+
+ verify(windowManager).removeView(viewCaptor.value)
+ verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value.title).isEqualTo("Name 2 Title")
+ assertThat(underTest.activeViews.size).isEqualTo(2)
+ reset(windowManager)
+
+ // While the id2 view is displayed, re-display the id1 view with new information
+ fakeClock.advanceTime(1000)
+ val updatedViewInfo = ViewInfo(
+ name = "name 1 with update",
+ windowTitle = "Name 1 Title",
+ id = "id1",
+ priority = ViewPriority.NORMAL,
+ timeoutMs = 3000,
+ )
+ underTest.displayView(updatedViewInfo)
+
+ verify(windowManager).removeView(viewCaptor.value)
+ verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value.title).isEqualTo("Name 1 Title")
+ assertThat(underTest.activeViews.size).isEqualTo(2)
+ reset(windowManager)
+
+ // WHEN the id1 view with new information times out
+ fakeClock.advanceTime(3001)
+
+ // THEN the id1 view disappears and the old id1 information is never displayed
+ verify(windowManager).removeView(viewCaptor.value)
+ verify(windowManager, never()).addView(any(), any())
+ assertThat(underTest.activeViews.size).isEqualTo(0)
+ }
+
+ @Test
+ fun oldViewUpdatedWhileNewViewDisplayed_usesNewTimeout() {
+ // First, display id1 view
+ underTest.displayView(
+ ViewInfo(
+ name = "name 1",
+ windowTitle = "Name 1 Title",
+ id = "id1",
+ priority = ViewPriority.NORMAL,
+ timeoutMs = 5000,
+ )
+ )
+
+ // Then, display a new id2 view
+ fakeClock.advanceTime(1000)
+ underTest.displayView(
+ ViewInfo(
+ name = "name 2",
+ windowTitle = "Name 2 Title",
+ id = "id2",
+ priority = ViewPriority.NORMAL,
+ timeoutMs = 3000,
+ )
+ )
+ reset(windowManager)
+
+ // While the id2 view is displayed, re-display the id1 view with new information *and a
+ // longer timeout*
+ fakeClock.advanceTime(1000)
+ val updatedViewInfo = ViewInfo(
+ name = "name 1 with update",
+ windowTitle = "Name 1 Title",
+ id = "id1",
+ priority = ViewPriority.NORMAL,
+ timeoutMs = 30000,
+ )
+ underTest.displayView(updatedViewInfo)
+
+ val viewCaptor = argumentCaptor<View>()
+ val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+ verify(windowManager).addView(capture(viewCaptor), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value.title).isEqualTo("Name 1 Title")
+ assertThat(underTest.activeViews.size).isEqualTo(2)
+ reset(windowManager)
+
+ // WHEN id1's *old* timeout occurs
+ fakeClock.advanceTime(3001)
+
+ // THEN id1 is still displayed because it was updated with a new timeout
+ verify(windowManager, never()).removeView(viewCaptor.value)
+ assertThat(underTest.activeViews.size).isEqualTo(1)
}
@Test
@@ -395,6 +964,7 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
verify(windowManager).removeView(any())
verify(logger).logViewRemoval(deviceId, reason)
+ verify(configurationController).removeCallback(any())
}
@Test
@@ -414,14 +984,15 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
inner class TestController(
context: Context,
- logger: TemporaryViewLogger,
+ logger: TemporaryViewLogger<ViewInfo>,
windowManager: WindowManager,
@Main mainExecutor: DelayableExecutor,
accessibilityManager: AccessibilityManager,
configurationController: ConfigurationController,
powerManager: PowerManager,
wakeLockBuilder: WakeLock.Builder,
- ) : TemporaryViewDisplayController<ViewInfo, TemporaryViewLogger>(
+ systemClock: SystemClock,
+ ) : TemporaryViewDisplayController<ViewInfo, TemporaryViewLogger<ViewInfo>>(
context,
logger,
windowManager,
@@ -431,6 +1002,7 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
powerManager,
R.layout.chipbar,
wakeLockBuilder,
+ systemClock,
) {
var mostRecentViewInfo: ViewInfo? = null
@@ -447,12 +1019,13 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
override fun start() {}
}
- inner class ViewInfo(
+ data class ViewInfo(
val name: String,
override val windowTitle: String = "Window Title",
override val wakeReason: String = "WAKE_REASON",
- override val timeoutMs: Int = 1,
+ override val timeoutMs: Int = TIMEOUT_MS.toInt(),
override val id: String = "id",
+ override val priority: ViewPriority = ViewPriority.NORMAL,
) : TemporaryViewInfo()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
index 116b8fe62b37..2e66b205bfd5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
@@ -32,7 +32,7 @@ import org.mockito.Mockito
@SmallTest
class TemporaryViewLoggerTest : SysuiTestCase() {
private lateinit var buffer: LogBuffer
- private lateinit var logger: TemporaryViewLogger
+ private lateinit var logger: TemporaryViewLogger<TemporaryViewInfo>
@Before
fun setUp() {
@@ -44,13 +44,22 @@ class TemporaryViewLoggerTest : SysuiTestCase() {
@Test
fun logViewAddition_bufferHasLog() {
- logger.logViewAddition("test id", "Test Window Title")
+ val info =
+ object : TemporaryViewInfo() {
+ override val id: String = "test id"
+ override val priority: ViewPriority = ViewPriority.CRITICAL
+ override val windowTitle: String = "Test Window Title"
+ override val wakeReason: String = "wake reason"
+ }
+
+ logger.logViewAddition(info)
val stringWriter = StringWriter()
buffer.dump(PrintWriter(stringWriter), tailLength = 0)
val actualString = stringWriter.toString()
assertThat(actualString).contains(TAG)
+ assertThat(actualString).contains("test id")
assertThat(actualString).contains("Test Window Title")
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
index 7014f93fba4a..2e4d8e74ad6e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
@@ -39,6 +39,7 @@ import com.android.systemui.common.shared.model.TintedIcon
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.temporarydisplay.ViewPriority
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
@@ -105,6 +106,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
viewUtil,
vibratorHelper,
fakeWakeLockBuilder,
+ fakeClock,
)
underTest.start()
}
@@ -408,6 +410,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
wakeReason = WAKE_REASON,
timeoutMs = TIMEOUT,
id = DEVICE_ID,
+ priority = ViewPriority.NORMAL,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
index beedf9f337bc..d5167b3890b9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
@@ -26,6 +26,7 @@ import com.android.systemui.plugins.FalsingManager
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.time.SystemClock
import com.android.systemui.util.view.ViewUtil
import com.android.systemui.util.wakelock.WakeLock
@@ -43,6 +44,7 @@ class FakeChipbarCoordinator(
viewUtil: ViewUtil,
vibratorHelper: VibratorHelper,
wakeLockBuilder: WakeLock.Builder,
+ systemClock: SystemClock,
) :
ChipbarCoordinator(
context,
@@ -57,6 +59,7 @@ class FakeChipbarCoordinator(
viewUtil,
vibratorHelper,
wakeLockBuilder,
+ systemClock,
) {
override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
// Just bypass the animation in tests
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 4fbbd799a3db..59c1c544cf88 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -26,8 +26,11 @@ import static android.accessibilityservice.AccessibilityTrace.FLAGS_USER_BROADCA
import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION;
import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL;
import static android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED;
+import static android.provider.Settings.Secure.CONTRAST_LEVEL;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
+import static android.view.accessibility.AccessibilityManager.CONTRAST_DEFAULT_VALUE;
+import static android.view.accessibility.AccessibilityManager.CONTRAST_NOT_SET;
import static android.view.accessibility.AccessibilityManager.ShortcutType;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
@@ -1902,6 +1905,16 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
return false;
}
+ private boolean readUiContrastLocked(AccessibilityUserState userState) {
+ float contrast = Settings.Secure.getFloatForUser(mContext.getContentResolver(),
+ CONTRAST_LEVEL, CONTRAST_DEFAULT_VALUE, userState.mUserId);
+ if (Math.abs(userState.getUiContrastLocked() - contrast) >= 1e-10) {
+ userState.setUiContrastLocked(contrast);
+ return true;
+ }
+ return false;
+ }
+
/**
* Performs {@link AccessibilityService}s delayed notification. The delay is configurable
* and denotes the period after the last event before notifying the service.
@@ -2568,6 +2581,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
somethingChanged |= readMagnificationModeForDefaultDisplayLocked(userState);
somethingChanged |= readMagnificationCapabilitiesLocked(userState);
somethingChanged |= readMagnificationFollowTypingLocked(userState);
+ somethingChanged |= readUiContrastLocked(userState);
return somethingChanged;
}
@@ -3709,6 +3723,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
return mProxyManager.unregisterProxy(displayId);
}
+ @Override public float getUiContrast() {
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
+ mTraceManager.logTrace(LOG_TAG + ".getUiContrast", FLAGS_ACCESSIBILITY_MANAGER);
+ }
+ synchronized (mLock) {
+ AccessibilityUserState userState = getCurrentUserStateLocked();
+ float contrast = userState.getUiContrastLocked();
+ if (contrast != CONTRAST_NOT_SET) return contrast;
+ readUiContrastLocked(userState);
+ return userState.getUiContrastLocked();
+ }
+ }
+
@Override
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
@@ -4156,6 +4183,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private final Uri mMagnificationFollowTypingUri = Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_FOLLOW_TYPING_ENABLED);
+ private final Uri mUiContrastUri = Settings.Secure.getUriFor(
+ CONTRAST_LEVEL);
+
public AccessibilityContentObserver(Handler handler) {
super(handler);
}
@@ -4196,6 +4226,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mMagnificationCapabilityUri, false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(
mMagnificationFollowTypingUri, false, this, UserHandle.USER_ALL);
+ contentResolver.registerContentObserver(
+ mUiContrastUri, false, this, UserHandle.USER_ALL);
}
@Override
@@ -4265,6 +4297,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
} else if (mMagnificationFollowTypingUri.equals(uri)) {
readMagnificationFollowTypingLocked(userState);
+ } else if (mUiContrastUri.equals(uri)) {
+ if (readUiContrastLocked(userState)) {
+ updateUiContrastLocked(userState);
+ }
}
}
}
@@ -4554,7 +4590,22 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
userState.getFocusColorLocked());
}));
});
+ }
+ private void updateUiContrastLocked(AccessibilityUserState userState) {
+ if (userState.mUserId != mCurrentUserId) {
+ return;
+ }
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_SERVICE_CLIENT)) {
+ mTraceManager.logTrace(LOG_TAG + ".updateUiContrastLocked",
+ FLAGS_ACCESSIBILITY_SERVICE_CLIENT, "userState=" + userState);
+ }
+ float contrast = userState.getUiContrastLocked();
+ mMainHandler.post(() -> {
+ broadcastToClients(userState, ignoreRemoteException(client -> {
+ client.mCallback.setUiContrast(contrast);
+ }));
+ });
}
public AccessibilityTraceManager getTraceManager() {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 0db169fd76c3..43730fce0cb7 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -26,6 +26,8 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
+import static android.view.accessibility.AccessibilityManager.CONTRAST_DEFAULT_VALUE;
+import static android.view.accessibility.AccessibilityManager.CONTRAST_NOT_SET;
import static android.view.accessibility.AccessibilityManager.ShortcutType;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
@@ -143,6 +145,8 @@ class AccessibilityUserState {
private final int mFocusStrokeWidthDefaultValue;
// The default value of the focus color.
private final int mFocusColorDefaultValue;
+ /** The color contrast in [-1, 1] */
+ private float mUiContrast = CONTRAST_DEFAULT_VALUE;
private Context mContext;
@@ -217,6 +221,7 @@ class AccessibilityUserState {
mFocusStrokeWidth = mFocusStrokeWidthDefaultValue;
mFocusColor = mFocusColorDefaultValue;
mMagnificationFollowTypingEnabled = true;
+ mUiContrast = CONTRAST_NOT_SET;
}
void addServiceLocked(AccessibilityServiceConnection serviceConnection) {
@@ -983,6 +988,7 @@ class AccessibilityUserState {
return mFocusColor;
}
+
/**
* Sets the stroke width and color of the focus rectangle.
*
@@ -1008,4 +1014,20 @@ class AccessibilityUserState {
}
return false;
}
+
+ /**
+ * Get the color contrast
+ * @return color contrast in [-1, 1]
+ */
+ public float getUiContrastLocked() {
+ return mUiContrast;
+ }
+
+ /**
+ * Set the color contrast
+ * @param contrast the new color contrast in [-1, 1]
+ */
+ public void setUiContrastLocked(float contrast) {
+ mUiContrast = contrast;
+ }
}
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 0cea3d0575c6..97b5d6ddf6b6 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -61,15 +61,19 @@ class InputController {
private static final AtomicLong sNextPhysId = new AtomicLong(1);
+ static final String NAVIGATION_TOUCHPAD_DEVICE_TYPE = "touchNavigation";
+
static final String PHYS_TYPE_DPAD = "Dpad";
static final String PHYS_TYPE_KEYBOARD = "Keyboard";
static final String PHYS_TYPE_MOUSE = "Mouse";
static final String PHYS_TYPE_TOUCHSCREEN = "Touchscreen";
+ static final String PHYS_TYPE_NAVIGATION_TOUCHPAD = "NavigationTouchpad";
@StringDef(prefix = { "PHYS_TYPE_" }, value = {
PHYS_TYPE_DPAD,
PHYS_TYPE_KEYBOARD,
PHYS_TYPE_MOUSE,
PHYS_TYPE_TOUCHSCREEN,
+ PHYS_TYPE_NAVIGATION_TOUCHPAD,
})
@Retention(RetentionPolicy.SOURCE)
@interface PhysType {
@@ -190,6 +194,28 @@ class InputController {
}
}
+ void createNavigationTouchpad(
+ @NonNull String deviceName,
+ int vendorId,
+ int productId,
+ @NonNull IBinder deviceToken,
+ int displayId,
+ int touchpadHeight,
+ int touchpadWidth) {
+ final String phys = createPhys(PHYS_TYPE_NAVIGATION_TOUCHPAD);
+ mInputManagerInternal.setTypeAssociation(phys, NAVIGATION_TOUCHPAD_DEVICE_TYPE);
+ try {
+ createDeviceInternal(InputDeviceDescriptor.TYPE_NAVIGATION_TOUCHPAD, deviceName,
+ vendorId, productId, deviceToken, displayId, phys,
+ () -> mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId,
+ phys, touchpadHeight, touchpadWidth));
+ } catch (DeviceCreationException e) {
+ mInputManagerInternal.unsetTypeAssociation(phys);
+ throw new RuntimeException(
+ "Failed to create virtual navigation touchpad device '" + deviceName + "'.", e);
+ }
+ }
+
void unregisterInputDevice(@NonNull IBinder token) {
synchronized (mLock) {
final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.remove(
@@ -207,7 +233,13 @@ class InputController {
InputDeviceDescriptor inputDeviceDescriptor) {
token.unlinkToDeath(inputDeviceDescriptor.getDeathRecipient(), /* flags= */ 0);
mNativeWrapper.closeUinput(inputDeviceDescriptor.getFileDescriptor());
+
InputManager.getInstance().removeUniqueIdAssociation(inputDeviceDescriptor.getPhys());
+ // Type associations are added in the case of navigation touchpads. Those should be removed
+ // once the input device gets closed.
+ if (inputDeviceDescriptor.getType() == InputDeviceDescriptor.TYPE_NAVIGATION_TOUCHPAD) {
+ mInputManagerInternal.unsetTypeAssociation(inputDeviceDescriptor.getPhys());
+ }
// Reset values to the default if all virtual mice are unregistered, or set display
// id if there's another mouse (choose the most recent). The inputDeviceDescriptor must be
@@ -509,11 +541,13 @@ class InputController {
static final int TYPE_MOUSE = 2;
static final int TYPE_TOUCHSCREEN = 3;
static final int TYPE_DPAD = 4;
+ static final int TYPE_NAVIGATION_TOUCHPAD = 5;
@IntDef(prefix = { "TYPE_" }, value = {
TYPE_KEYBOARD,
TYPE_MOUSE,
TYPE_TOUCHSCREEN,
TYPE_DPAD,
+ TYPE_NAVIGATION_TOUCHPAD,
})
@Retention(RetentionPolicy.SOURCE)
@interface Type {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 58198612d80e..12ad9f1cf580 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -53,6 +53,7 @@ import android.hardware.input.VirtualMouseButtonEvent;
import android.hardware.input.VirtualMouseConfig;
import android.hardware.input.VirtualMouseRelativeEvent;
import android.hardware.input.VirtualMouseScrollEvent;
+import android.hardware.input.VirtualNavigationTouchpadConfig;
import android.hardware.input.VirtualTouchEvent;
import android.hardware.input.VirtualTouchscreenConfig;
import android.os.Binder;
@@ -108,7 +109,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
private VirtualAudioController mVirtualAudioController;
@VisibleForTesting
final Set<Integer> mVirtualDisplayIds = new ArraySet<>();
- private final OnDeviceCloseListener mListener;
+ private final OnDeviceCloseListener mOnDeviceCloseListener;
private final IBinder mAppToken;
private final VirtualDeviceParams mParams;
private final Map<Integer, PowerManager.WakeLock> mPerDisplayWakelocks = new ArrayMap<>();
@@ -155,7 +156,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
IBinder token,
int ownerUid,
int deviceId,
- OnDeviceCloseListener listener,
+ OnDeviceCloseListener onDeviceCloseListener,
PendingTrampolineCallback pendingTrampolineCallback,
IVirtualDeviceActivityListener activityListener,
Consumer<ArraySet<Integer>> runningAppsChangedCallback,
@@ -168,7 +169,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
deviceId,
/* inputController= */ null,
/* sensorController= */ null,
- listener,
+ onDeviceCloseListener,
pendingTrampolineCallback,
activityListener,
runningAppsChangedCallback,
@@ -184,7 +185,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
int deviceId,
InputController inputController,
SensorController sensorController,
- OnDeviceCloseListener listener,
+ OnDeviceCloseListener onDeviceCloseListener,
PendingTrampolineCallback pendingTrampolineCallback,
IVirtualDeviceActivityListener activityListener,
Consumer<ArraySet<Integer>> runningAppsChangedCallback,
@@ -212,7 +213,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
} else {
mSensorController = sensorController;
}
- mListener = listener;
+ mOnDeviceCloseListener = onDeviceCloseListener;
try {
token.linkToDeath(this, 0);
} catch (RemoteException e) {
@@ -330,7 +331,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
mVirtualAudioController = null;
}
}
- mListener.onClose(mAssociationInfo.getId());
+ mOnDeviceCloseListener.onClose(mDeviceId);
mAppToken.unlinkToDeath(this, 0);
final long ident = Binder.clearCallingIdentity();
@@ -491,6 +492,38 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
@Override // Binder call
+ public void createVirtualNavigationTouchpad(VirtualNavigationTouchpadConfig config,
+ @NonNull IBinder deviceToken) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+ "Permission required to create a virtual navigation touchpad");
+ synchronized (mVirtualDeviceLock) {
+ if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
+ throw new SecurityException(
+ "Cannot create a virtual navigation touchpad for a display not associated "
+ + "with this virtual device");
+ }
+ }
+ int touchpadHeight = config.getHeight();
+ int touchpadWidth = config.getWidth();
+ if (touchpadHeight <= 0 || touchpadWidth <= 0) {
+ throw new IllegalArgumentException(
+ "Cannot create a virtual navigation touchpad, touchpad dimensions must be positive."
+ + " Got: (" + touchpadHeight + ", " + touchpadWidth + ")");
+ }
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mInputController.createNavigationTouchpad(
+ config.getInputDeviceName(), config.getVendorId(),
+ config.getProductId(), deviceToken, config.getAssociatedDisplayId(),
+ touchpadHeight, touchpadWidth);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
public void unregisterInputDevice(IBinder token) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
@@ -650,6 +683,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
@Override
protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
fout.println(" VirtualDevice: ");
+ fout.println(" mDeviceId: " + mDeviceId);
fout.println(" mAssociationId: " + mAssociationInfo.getId());
fout.println(" mParams: " + mParams);
fout.println(" mVirtualDisplayIds: ");
@@ -839,7 +873,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
interface OnDeviceCloseListener {
- void onClose(int associationId);
+ void onClose(int deviceId);
}
interface PendingTrampolineCallback {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 0d8bba307cd5..da2c5162e6e1 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -90,14 +90,20 @@ public class VirtualDeviceManagerService extends SystemService {
new SparseArray<>();
/**
- * Mapping from CDM association IDs to virtual devices. Only one virtual device is allowed for
- * each CDM associated device.
+ * Mapping from device IDs to CameraAccessControllers.
+ */
+ @GuardedBy("mVirtualDeviceManagerLock")
+ private final SparseArray<CameraAccessController> mCameraAccessControllersByDeviceId =
+ new SparseArray<>();
+
+ /**
+ * Mapping from device IDs to virtual devices.
*/
@GuardedBy("mVirtualDeviceManagerLock")
private final SparseArray<VirtualDeviceImpl> mVirtualDevices = new SparseArray<>();
/**
- * Mapping from CDM association IDs to app UIDs running on the corresponding virtual device.
+ * Mapping from device IDs to app UIDs running on the corresponding virtual device.
*/
@GuardedBy("mVirtualDeviceManagerLock")
private final SparseArray<ArraySet<Integer>> mAppsOnVirtualDevices = new SparseArray<>();
@@ -160,7 +166,7 @@ public class VirtualDeviceManagerService extends SystemService {
@GuardedBy("mVirtualDeviceManagerLock")
private boolean isValidVirtualDeviceLocked(IVirtualDevice virtualDevice) {
try {
- return mVirtualDevices.contains(virtualDevice.getAssociationId());
+ return mVirtualDevices.contains(virtualDevice.getDeviceId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -230,9 +236,9 @@ public class VirtualDeviceManagerService extends SystemService {
}
@VisibleForTesting
- void notifyRunningAppsChanged(int associationId, ArraySet<Integer> uids) {
+ void notifyRunningAppsChanged(int deviceId, ArraySet<Integer> uids) {
synchronized (mVirtualDeviceManagerLock) {
- mAppsOnVirtualDevices.put(associationId, uids);
+ mAppsOnVirtualDevices.put(deviceId, uids);
}
mLocalService.onAppsOnVirtualDeviceChanged();
}
@@ -240,7 +246,7 @@ public class VirtualDeviceManagerService extends SystemService {
@VisibleForTesting
void addVirtualDevice(VirtualDeviceImpl virtualDevice) {
synchronized (mVirtualDeviceManagerLock) {
- mVirtualDevices.put(virtualDevice.getAssociationId(), virtualDevice);
+ mVirtualDevices.put(virtualDevice.getDeviceId(), virtualDevice);
}
}
@@ -268,60 +274,26 @@ public class VirtualDeviceManagerService extends SystemService {
throw new IllegalArgumentException("No association with ID " + associationId);
}
synchronized (mVirtualDeviceManagerLock) {
- if (mVirtualDevices.contains(associationId)) {
- throw new IllegalStateException(
- "Virtual device for association ID " + associationId
- + " already exists");
- }
final int userId = UserHandle.getUserId(callingUid);
final CameraAccessController cameraAccessController =
mCameraAccessControllers.get(userId);
- final int uniqueId = sNextUniqueIndex.getAndIncrement();
-
+ final int deviceId = sNextUniqueIndex.getAndIncrement();
VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl(getContext(),
- associationInfo, token, callingUid, uniqueId,
- new VirtualDeviceImpl.OnDeviceCloseListener() {
- @Override
- public void onClose(int associationId) {
- synchronized (mVirtualDeviceManagerLock) {
- VirtualDeviceImpl removedDevice =
- mVirtualDevices.removeReturnOld(associationId);
- if (removedDevice != null) {
- Intent i = new Intent(
- VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED);
- i.putExtra(
- VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID,
- removedDevice.getDeviceId());
- i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- final long identity = Binder.clearCallingIdentity();
- try {
- getContext().sendBroadcastAsUser(i, UserHandle.ALL);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- mAppsOnVirtualDevices.remove(associationId);
- if (cameraAccessController != null) {
- cameraAccessController.stopObservingIfNeeded();
- } else {
- Slog.w(TAG, "cameraAccessController not found for user "
- + userId);
- }
- }
- }
- },
+ associationInfo, token, callingUid, deviceId,
+ /* onDeviceCloseListener= */ this::onDeviceClosed,
this, activityListener,
runningUids -> {
cameraAccessController.blockCameraAccessIfNeeded(runningUids);
- notifyRunningAppsChanged(associationInfo.getId(), runningUids);
+ notifyRunningAppsChanged(deviceId, runningUids);
},
params);
if (cameraAccessController != null) {
cameraAccessController.startObservingIfNeeded();
+ mCameraAccessControllersByDeviceId.put(deviceId, cameraAccessController);
} else {
Slog.w(TAG, "cameraAccessController not found for user " + userId);
}
- mVirtualDevices.put(associationInfo.getId(), virtualDevice);
+ mVirtualDevices.put(deviceId, virtualDevice);
return virtualDevice;
}
}
@@ -338,7 +310,7 @@ public class VirtualDeviceManagerService extends SystemService {
}
VirtualDeviceImpl virtualDeviceImpl;
synchronized (mVirtualDeviceManagerLock) {
- virtualDeviceImpl = mVirtualDevices.get(virtualDevice.getAssociationId());
+ virtualDeviceImpl = mVirtualDevices.get(virtualDevice.getDeviceId());
if (virtualDeviceImpl == null) {
throw new SecurityException("Invalid VirtualDevice");
}
@@ -419,8 +391,7 @@ public class VirtualDeviceManagerService extends SystemService {
@Nullable
private AssociationInfo getAssociationInfo(String packageName, int associationId) {
final int callingUserId = getCallingUserHandle().getIdentifier();
- final List<AssociationInfo> associations =
- mAllAssociations.get(callingUserId);
+ final List<AssociationInfo> associations = mAllAssociations.get(callingUserId);
if (associations != null) {
final int associationSize = associations.size();
for (int i = 0; i < associationSize; i++) {
@@ -436,6 +407,29 @@ public class VirtualDeviceManagerService extends SystemService {
return null;
}
+ private void onDeviceClosed(int deviceId) {
+ synchronized (mVirtualDeviceManagerLock) {
+ mVirtualDevices.remove(deviceId);
+ Intent i = new Intent(VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED);
+ i.putExtra(VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID, deviceId);
+ i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ getContext().sendBroadcastAsUser(i, UserHandle.ALL);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ mAppsOnVirtualDevices.remove(deviceId);
+ final CameraAccessController cameraAccessController =
+ mCameraAccessControllersByDeviceId.removeReturnOld(deviceId);
+ if (cameraAccessController != null) {
+ cameraAccessController.stopObservingIfNeeded();
+ } else {
+ Slog.w(TAG, "cameraAccessController not found for device Id " + deviceId);
+ }
+ }
+ }
+
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b62937847ea9..045c757767e4 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -13577,9 +13577,10 @@ public class ActivityManagerService extends IActivityManager.Stub
// updating their receivers to be exempt from this requirement until their receivers
// are flagged.
if (requireExplicitFlagForDynamicReceivers) {
- if ("com.google.android.apps.messaging".equals(callerPackage)) {
- // Note, a versionCode check for this package is not performed because it could
- // cause breakage with a subsequent update outside the system image.
+ if ("com.shannon.imsservice".equals(callerPackage)) {
+ // Note, a versionCode check for this package is not performed because this
+ // package consumes the SecurityException, so it wouldn't be caught during
+ // presubmit.
requireExplicitFlagForDynamicReceivers = false;
}
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 280256fb49d7..e5123eff9095 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -1571,12 +1571,6 @@ class UserController implements Handler.Callback {
checkCallingHasOneOfThosePermissions("startUserOnSecondaryDisplay",
MANAGE_USERS, INTERACT_ACROSS_USERS);
- // DEFAULT_DISPLAY is used for the current foreground user only
- // TODO(b/245939659): might need to move this check to UserVisibilityMediator to support
- // passenger-only screens
- Preconditions.checkArgument(displayId != Display.DEFAULT_DISPLAY,
- "Cannot use DEFAULT_DISPLAY");
-
try {
return startUserNoChecks(userId, displayId, USER_START_MODE_BACKGROUND_VISIBLE,
/* unlockListener= */ null);
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
index 587fb0410bca..ac25f4edfdb7 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
@@ -20,7 +20,7 @@ import static android.app.AppOpsManager.OP_NONE;
import static android.app.AppOpsManager.WATCH_FOREGROUND_CHANGES;
import static android.app.AppOpsManager.opRestrictsRead;
-import static com.android.server.appop.AppOpsServiceImpl.ModeCallback.ALL_OPS;
+import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
import android.Manifest;
import android.annotation.NonNull;
@@ -85,8 +85,8 @@ public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface
AppOpsCheckingServiceImpl(PersistenceScheduler persistenceScheduler,
- @NonNull Object lock, Handler handler, Context context,
- SparseArray<int[]> switchedOps) {
+ @NonNull Object lock, Handler handler, Context context,
+ SparseArray<int[]> switchedOps) {
this.mPersistenceScheduler = persistenceScheduler;
this.mLock = lock;
this.mHandler = handler;
@@ -218,7 +218,7 @@ public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface
}
@Override
- public boolean arePackageModesDefault(@NonNull String packageMode, @UserIdInt int userId) {
+ public boolean arePackageModesDefault(String packageMode, @UserIdInt int userId) {
synchronized (mLock) {
ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId, null);
if (packageModes == null) {
@@ -490,16 +490,15 @@ public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface
}
@Override
- public SparseBooleanArray evalForegroundUidOps(int uid,
- @Nullable SparseBooleanArray foregroundOps) {
+ public SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps) {
synchronized (mLock) {
return evalForegroundOps(mUidModes.get(uid), foregroundOps);
}
}
@Override
- public SparseBooleanArray evalForegroundPackageOps(@NonNull String packageName,
- @Nullable SparseBooleanArray foregroundOps, @UserIdInt int userId) {
+ public SparseBooleanArray evalForegroundPackageOps(String packageName,
+ SparseBooleanArray foregroundOps, @UserIdInt int userId) {
synchronized (mLock) {
ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId, null);
return evalForegroundOps(packageModes == null ? null : packageModes.get(packageName),
@@ -538,8 +537,8 @@ public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface
}
@Override
- public boolean dumpListeners(int dumpOp, int dumpUid, @Nullable String dumpPackage,
- @NonNull PrintWriter printWriter) {
+ public boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage,
+ PrintWriter printWriter) {
boolean needSep = false;
if (mOpModeWatchers.size() > 0) {
boolean printedHeader = false;
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
index ef3e3685401f..d8d0d48965ea 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
@@ -103,7 +103,7 @@ public interface AppOpsCheckingServiceInterface {
* @param packageName package name.
* @param userId user id associated with the package.
*/
- boolean arePackageModesDefault(@NonNull String packageName, @UserIdInt int userId);
+ boolean arePackageModesDefault(String packageName, @UserIdInt int userId);
/**
* Stop tracking app-op modes for all uid and packages.
@@ -184,7 +184,7 @@ public interface AppOpsCheckingServiceInterface {
* @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true.
* @return foregroundOps.
*/
- SparseBooleanArray evalForegroundUidOps(int uid, @Nullable SparseBooleanArray foregroundOps);
+ SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps);
/**
* Go over the list of app-ops for the package name and mark app-ops with MODE_FOREGROUND in
@@ -194,8 +194,8 @@ public interface AppOpsCheckingServiceInterface {
* @param userId user id associated with the package.
* @return foregroundOps.
*/
- SparseBooleanArray evalForegroundPackageOps(@NonNull String packageName,
- @Nullable SparseBooleanArray foregroundOps, @UserIdInt int userId);
+ SparseBooleanArray evalForegroundPackageOps(String packageName,
+ SparseBooleanArray foregroundOps, @UserIdInt int userId);
/**
* Dump op mode and package mode listeners and their details.
@@ -205,6 +205,5 @@ public interface AppOpsCheckingServiceInterface {
* @param dumpPackage if not null and if dumpOp is -1, dumps watchers for the package name.
* @param printWriter writer to dump to.
*/
- boolean dumpListeners(int dumpOp, int dumpUid, @Nullable String dumpPackage,
- @NonNull PrintWriter printWriter);
+ boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage, PrintWriter printWriter);
}
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java
deleted file mode 100644
index 44360028704e..000000000000
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java
+++ /dev/null
@@ -1,279 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.appop;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.app.AppOpsManager;
-import android.os.Trace;
-import android.util.ArraySet;
-import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
-
-import java.io.PrintWriter;
-
-/**
- * Surrounds all AppOpsCheckingServiceInterface method calls with Trace.traceBegin and
- * Trace.traceEnd. These traces are used for performance testing.
- */
-public class AppOpsCheckingServiceTracingDecorator implements AppOpsCheckingServiceInterface {
- private static final long TRACE_TAG = Trace.TRACE_TAG_SYSTEM_SERVER;
- private final AppOpsCheckingServiceInterface mService;
-
- AppOpsCheckingServiceTracingDecorator(
- @NonNull AppOpsCheckingServiceInterface appOpsCheckingServiceInterface) {
- mService = appOpsCheckingServiceInterface;
- }
-
- @Override
- public SparseIntArray getNonDefaultUidModes(int uid) {
- Trace.traceBegin(TRACE_TAG,
- "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getNonDefaultUidModes");
- try {
- return mService.getNonDefaultUidModes(uid);
- } finally {
- Trace.traceEnd(TRACE_TAG);
- }
- }
-
- @Override
- public int getUidMode(int uid, int op) {
- Trace.traceBegin(TRACE_TAG, "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getUidMode");
- try {
- return mService.getUidMode(uid, op);
- } finally {
- Trace.traceEnd(TRACE_TAG);
- }
- }
-
- @Override
- public boolean setUidMode(int uid, int op, @AppOpsManager.Mode int mode) {
- Trace.traceBegin(TRACE_TAG, "TaggedTracingAppOpsCheckingServiceInterfaceImpl#setUidMode");
- try {
- return mService.setUidMode(uid, op, mode);
- } finally {
- Trace.traceEnd(TRACE_TAG);
- }
- }
-
- @Override
- public int getPackageMode(@NonNull String packageName, int op, @UserIdInt int userId) {
- Trace.traceBegin(TRACE_TAG,
- "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getPackageMode");
- try {
- return mService.getPackageMode(packageName, op, userId);
- } finally {
- Trace.traceEnd(TRACE_TAG);
- }
- }
-
- @Override
- public void setPackageMode(@NonNull String packageName, int op, @AppOpsManager.Mode int mode,
- @UserIdInt int userId) {
- Trace.traceBegin(TRACE_TAG,
- "TaggedTracingAppOpsCheckingServiceInterfaceImpl#setPackageMode");
- try {
- mService.setPackageMode(packageName, op, mode, userId);
- } finally {
- Trace.traceEnd(TRACE_TAG);
- }
- }
-
- @Override
- public boolean removePackage(@NonNull String packageName, @UserIdInt int userId) {
- Trace.traceBegin(TRACE_TAG,
- "TaggedTracingAppOpsCheckingServiceInterfaceImpl#removePackage");
- try {
- return mService.removePackage(packageName, userId);
- } finally {
- Trace.traceEnd(TRACE_TAG);
- }
- }
-
- @Override
- public void removeUid(int uid) {
- Trace.traceBegin(TRACE_TAG,
- "TaggedTracingAppOpsCheckingServiceInterfaceImpl#removeUid");
- try {
- mService.removeUid(uid);
- } finally {
- Trace.traceEnd(TRACE_TAG);
- }
- }
-
- @Override
- public boolean areUidModesDefault(int uid) {
- Trace.traceBegin(TRACE_TAG,
- "TaggedTracingAppOpsCheckingServiceInterfaceImpl#areUidModesDefault");
- try {
- return mService.areUidModesDefault(uid);
- } finally {
- Trace.traceEnd(TRACE_TAG);
- }
- }
-
- @Override
- public boolean arePackageModesDefault(String packageName, @UserIdInt int userId) {
- Trace.traceBegin(TRACE_TAG,
- "TaggedTracingAppOpsCheckingServiceInterfaceImpl#arePackageModesDefault");
- try {
- return mService.arePackageModesDefault(packageName, userId);
- } finally {
- Trace.traceEnd(TRACE_TAG);
- }
- }
-
- @Override
- public void clearAllModes() {
- Trace.traceBegin(TRACE_TAG,
- "TaggedTracingAppOpsCheckingServiceInterfaceImpl#clearAllModes");
- try {
- mService.clearAllModes();
- } finally {
- Trace.traceEnd(TRACE_TAG);
- }
- }
-
- @Override
- public void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener,
- int op) {
- Trace.traceBegin(TRACE_TAG,
- "TaggedTracingAppOpsCheckingServiceInterfaceImpl#startWatchingOpModeChanged");
- try {
- mService.startWatchingOpModeChanged(changedListener, op);
- } finally {
- Trace.traceEnd(TRACE_TAG);
- }
- }
-
- @Override
- public void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener,
- @NonNull String packageName) {
- Trace.traceBegin(TRACE_TAG,
- "TaggedTracingAppOpsCheckingServiceInterfaceImpl#startWatchingPackageModeChanged");
- try {
- mService.startWatchingPackageModeChanged(changedListener, packageName);
- } finally {
- Trace.traceEnd(TRACE_TAG);
- }
- }
-
- @Override
- public void removeListener(@NonNull OnOpModeChangedListener changedListener) {
- Trace.traceBegin(TRACE_TAG,
- "TaggedTracingAppOpsCheckingServiceInterfaceImpl#removeListener");
- try {
- mService.removeListener(changedListener);
- } finally {
- Trace.traceEnd(TRACE_TAG);
- }
- }
-
- @Override
- public ArraySet<OnOpModeChangedListener> getOpModeChangedListeners(int op) {
- Trace.traceBegin(TRACE_TAG,
- "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getOpModeChangedListeners");
- try {
- return mService.getOpModeChangedListeners(op);
- } finally {
- Trace.traceEnd(TRACE_TAG);
- }
- }
-
- @Override
- public ArraySet<OnOpModeChangedListener> getPackageModeChangedListeners(
- @NonNull String packageName) {
- Trace.traceBegin(TRACE_TAG,
- "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getPackageModeChangedListeners");
- try {
- return mService.getPackageModeChangedListeners(packageName);
- } finally {
- Trace.traceEnd(TRACE_TAG);
- }
- }
-
- @Override
- public void notifyWatchersOfChange(int op, int uid) {
- Trace.traceBegin(TRACE_TAG,
- "TaggedTracingAppOpsCheckingServiceInterfaceImpl#notifyWatchersOfChange");
- try {
- mService.notifyWatchersOfChange(op, uid);
- } finally {
- Trace.traceEnd(TRACE_TAG);
- }
- }
-
- @Override
- public void notifyOpChanged(@NonNull OnOpModeChangedListener changedListener, int op, int uid,
- @Nullable String packageName) {
- Trace.traceBegin(TRACE_TAG,
- "TaggedTracingAppOpsCheckingServiceInterfaceImpl#notifyOpChanged");
- try {
- mService.notifyOpChanged(changedListener, op, uid, packageName);
- } finally {
- Trace.traceEnd(TRACE_TAG);
- }
- }
-
- @Override
- public void notifyOpChangedForAllPkgsInUid(int op, int uid, boolean onlyForeground,
- @Nullable OnOpModeChangedListener callbackToIgnore) {
- Trace.traceBegin(TRACE_TAG,
- "TaggedTracingAppOpsCheckingServiceInterfaceImpl#notifyOpChangedForAllPkgsInUid");
- try {
- mService.notifyOpChangedForAllPkgsInUid(op, uid, onlyForeground, callbackToIgnore);
- } finally {
- Trace.traceEnd(TRACE_TAG);
- }
- }
-
- @Override
- public SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps) {
- Trace.traceBegin(TRACE_TAG,
- "TaggedTracingAppOpsCheckingServiceInterfaceImpl#evalForegroundUidOps");
- try {
- return mService.evalForegroundUidOps(uid, foregroundOps);
- } finally {
- Trace.traceEnd(TRACE_TAG);
- }
- }
-
- @Override
- public SparseBooleanArray evalForegroundPackageOps(String packageName,
- SparseBooleanArray foregroundOps, @UserIdInt int userId) {
- Trace.traceBegin(TRACE_TAG,
- "TaggedTracingAppOpsCheckingServiceInterfaceImpl#evalForegroundPackageOps");
- try {
- return mService.evalForegroundPackageOps(packageName, foregroundOps, userId);
- } finally {
- Trace.traceEnd(TRACE_TAG);
- }
- }
-
- @Override
- public boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage,
- PrintWriter printWriter) {
- Trace.traceBegin(TRACE_TAG,
- "TaggedTracingAppOpsCheckingServiceInterfaceImpl#dumpListeners");
- try {
- return mService.dumpListeners(dumpOp, dumpUid, dumpPackage, printWriter);
- } finally {
- Trace.traceEnd(TRACE_TAG);
- }
- }
-}
diff --git a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
index af5b07e0bffc..f51200f2bf0c 100644
--- a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
@@ -42,7 +42,7 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions {
private Context mContext;
private Handler mHandler;
- private AppOpsCheckingServiceInterface mAppOpsServiceInterface;
+ private AppOpsCheckingServiceInterface mAppOpsCheckingServiceInterface;
// Map from (Object token) to (int code) to (boolean restricted)
private final ArrayMap<Object, SparseBooleanArray> mGlobalRestrictions = new ArrayMap<>();
@@ -56,10 +56,10 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions {
mUserRestrictionExcludedPackageTags = new ArrayMap<>();
public AppOpsRestrictionsImpl(Context context, Handler handler,
- AppOpsCheckingServiceInterface appOpsServiceInterface) {
+ AppOpsCheckingServiceInterface appOpsCheckingServiceInterface) {
mContext = context;
mHandler = handler;
- mAppOpsServiceInterface = appOpsServiceInterface;
+ mAppOpsCheckingServiceInterface = appOpsCheckingServiceInterface;
}
@Override
@@ -219,7 +219,7 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions {
int restrictedCodesSize = allUserRestrictedCodes.size();
for (int j = 0; j < restrictedCodesSize; j++) {
int code = allUserRestrictedCodes.keyAt(j);
- mHandler.post(() -> mAppOpsServiceInterface.notifyWatchersOfChange(code, UID_ANY));
+ mHandler.post(() -> mAppOpsCheckingServiceInterface.notifyWatchersOfChange(code, UID_ANY));
}
}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 39338c6f43ad..934542291334 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -18,22 +18,56 @@ package com.android.server.appop;
import static android.app.AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE;
import static android.app.AppOpsManager.ATTRIBUTION_FLAG_TRUSTED;
+import static android.app.AppOpsManager.CALL_BACK_ON_SWITCHED_OP;
+import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG;
+import static android.app.AppOpsManager.FILTER_BY_OP_NAMES;
+import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME;
+import static android.app.AppOpsManager.FILTER_BY_UID;
+import static android.app.AppOpsManager.HISTORY_FLAG_GET_ATTRIBUTION_CHAINS;
+import static android.app.AppOpsManager.HistoricalOpsRequestFilter;
+import static android.app.AppOpsManager.KEY_BG_STATE_SETTLE_TIME;
+import static android.app.AppOpsManager.KEY_FG_SERVICE_STATE_SETTLE_TIME;
+import static android.app.AppOpsManager.KEY_TOP_STATE_SETTLE_TIME;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_DEFAULT;
+import static android.app.AppOpsManager.MODE_ERRORED;
+import static android.app.AppOpsManager.MODE_FOREGROUND;
+import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.OP_CAMERA;
import static android.app.AppOpsManager.OP_FLAGS_ALL;
import static android.app.AppOpsManager.OP_FLAG_SELF;
import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
import static android.app.AppOpsManager.OP_NONE;
+import static android.app.AppOpsManager.OP_PLAY_AUDIO;
+import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
+import static android.app.AppOpsManager.OP_RECORD_AUDIO;
+import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
+import static android.app.AppOpsManager.OP_SCHEDULE_EXACT_ALARM;
+import static android.app.AppOpsManager.OP_VIBRATE;
+import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED;
+import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED;
+import static android.app.AppOpsManager.OpEventProxyInfo;
+import static android.app.AppOpsManager.RestrictionBypass;
import static android.app.AppOpsManager.SAMPLING_STRATEGY_BOOT_TIME_SAMPLING;
import static android.app.AppOpsManager.SAMPLING_STRATEGY_RARELY_USED;
import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM;
import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM_OPS;
+import static android.app.AppOpsManager.SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE;
import static android.app.AppOpsManager._NUM_OP;
+import static android.app.AppOpsManager.extractFlagsFromKey;
+import static android.app.AppOpsManager.extractUidStateFromKey;
+import static android.app.AppOpsManager.modeToName;
+import static android.app.AppOpsManager.opAllowSystemBypassRestriction;
import static android.app.AppOpsManager.opRestrictsRead;
+import static android.app.AppOpsManager.opToName;
import static android.app.AppOpsManager.opToPublicName;
+import static android.content.Intent.ACTION_PACKAGE_REMOVED;
+import static android.content.Intent.EXTRA_REPLACING;
import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP;
+import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
+
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -42,16 +76,21 @@ import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
import android.app.AppOpsManager;
+import android.app.AppOpsManager.AttributedOpEntry;
import android.app.AppOpsManager.AttributionFlags;
import android.app.AppOpsManager.HistoricalOps;
+import android.app.AppOpsManager.Mode;
+import android.app.AppOpsManager.OpEntry;
import android.app.AppOpsManager.OpFlags;
import android.app.AppOpsManagerInternal;
import android.app.AppOpsManagerInternal.CheckOpsDelegate;
import android.app.AsyncNotedAppOp;
import android.app.RuntimeAppOpAccessMessage;
import android.app.SyncNotedAppOp;
+import android.app.admin.DevicePolicyManagerInternal;
import android.content.AttributionSource;
import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -59,11 +98,15 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.PermissionInfo;
+import android.database.ContentObserver;
import android.hardware.camera2.CameraDevice.CAMERA_AUDIO_RESTRICTION;
+import android.net.Uri;
import android.os.AsyncTask;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.PackageTagsList;
import android.os.Process;
@@ -74,14 +117,22 @@ import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.ShellCommand;
+import android.os.SystemClock;
import android.os.UserHandle;
+import android.os.storage.StorageManagerInternal;
+import android.permission.PermissionManager;
+import android.provider.Settings;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.AtomicFile;
+import android.util.KeyValueListParser;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.TimeUtils;
+import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.Immutable;
@@ -93,37 +144,61 @@ import com.android.internal.app.IAppOpsNotedCallback;
import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IAppOpsStartedCallback;
import com.android.internal.app.MessageSamplingConfig;
+import com.android.internal.compat.IPlatformCompat;
+import com.android.internal.os.Clock;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
+import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalServices;
+import com.android.server.LockGuard;
+import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemServiceManager;
import com.android.server.pm.PackageList;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.permission.PermissionManagerServiceInternal;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.component.ParsedAttribution;
import com.android.server.policy.AppOpsPolicy;
+import dalvik.annotation.optimization.NeverCompile;
+
+import libcore.util.EmptyArray;
+
import org.json.JSONException;
import org.json.JSONObject;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Scanner;
+import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Consumer;
-/**
- * The system service component to {@link AppOpsManager}.
- */
-public class AppOpsService extends IAppOpsService.Stub {
-
- private final AppOpsServiceInterface mAppOpsService;
-
+public class AppOpsService extends IAppOpsService.Stub implements PersistenceScheduler {
static final String TAG = "AppOps";
static final boolean DEBUG = false;
@@ -132,19 +207,74 @@ public class AppOpsService extends IAppOpsService.Stub {
*/
private final ArraySet<NoteOpTrace> mNoteOpCallerStacktraces = new ArraySet<>();
+ /**
+ * Sentinel integer version to denote that there was no appops.xml found on boot.
+ * This will happen when a device boots with no existing userdata.
+ */
+ private static final int NO_FILE_VERSION = -2;
+
+ /**
+ * Sentinel integer version to denote that there was no version in the appops.xml found on boot.
+ * This means the file is coming from a build before versioning was added.
+ */
+ private static final int NO_VERSION = -1;
+
+ /** Increment by one every time and add the corresponding upgrade logic in
+ * {@link #upgradeLocked(int)} below. The first version was 1 */
+ static final int CURRENT_VERSION = 2;
+
+ /**
+ * This stores the version of appops.xml seen at boot. If this is smaller than
+ * {@link #CURRENT_VERSION}, then we will run {@link #upgradeLocked(int)} on startup.
+ */
+ private int mVersionAtBoot = NO_FILE_VERSION;
+
+ // Write at most every 30 minutes.
+ static final long WRITE_DELAY = DEBUG ? 1000 : 30*60*1000;
+
// Constant meaning that any UID should be matched when dispatching callbacks
private static final int UID_ANY = -2;
- private static final int MAX_UNFORWARDED_OPS = 10;
+ private static final int[] OPS_RESTRICTED_ON_SUSPEND = {
+ OP_PLAY_AUDIO,
+ OP_RECORD_AUDIO,
+ OP_CAMERA,
+ OP_VIBRATE,
+ };
+ private static final int MAX_UNFORWARDED_OPS = 10;
+ private static final int MAX_UNUSED_POOLED_OBJECTS = 3;
private static final int RARELY_USED_PACKAGES_INITIALIZATION_DELAY_MILLIS = 300000;
final Context mContext;
+ final AtomicFile mFile;
private final @Nullable File mNoteOpCallerStacktracesFile;
final Handler mHandler;
+ /**
+ * Pool for {@link AttributedOp.OpEventProxyInfoPool} to avoid to constantly reallocate new
+ * objects
+ */
+ @GuardedBy("this")
+ final AttributedOp.OpEventProxyInfoPool mOpEventProxyInfoPool =
+ new AttributedOp.OpEventProxyInfoPool(MAX_UNUSED_POOLED_OBJECTS);
+
+ /**
+ * Pool for {@link AttributedOp.InProgressStartOpEventPool} to avoid to constantly reallocate
+ * new objects
+ */
+ @GuardedBy("this")
+ final AttributedOp.InProgressStartOpEventPool mInProgressStartOpEventPool =
+ new AttributedOp.InProgressStartOpEventPool(mOpEventProxyInfoPool,
+ MAX_UNUSED_POOLED_OBJECTS);
+
private final AppOpsManagerInternalImpl mAppOpsManagerInternal
= new AppOpsManagerInternalImpl();
+ @Nullable private final DevicePolicyManagerInternal dpmi =
+ LocalServices.getService(DevicePolicyManagerInternal.class);
+
+ private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface(
+ ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
/**
* Registered callbacks, called from {@link #collectAsyncNotedOp}.
@@ -171,9 +301,54 @@ public class AppOpsService extends IAppOpsService.Stub {
boolean mWriteNoteOpsScheduled;
+ boolean mWriteScheduled;
+ boolean mFastWriteScheduled;
+ final Runnable mWriteRunner = new Runnable() {
+ public void run() {
+ synchronized (AppOpsService.this) {
+ mWriteScheduled = false;
+ mFastWriteScheduled = false;
+ AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
+ @Override protected Void doInBackground(Void... params) {
+ writeState();
+ return null;
+ }
+ };
+ task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[])null);
+ }
+ }
+ };
+
+ @GuardedBy("this")
+ @VisibleForTesting
+ final SparseArray<UidState> mUidStates = new SparseArray<>();
+
+ volatile @NonNull HistoricalRegistry mHistoricalRegistry = new HistoricalRegistry(this);
+
+ /*
+ * These are app op restrictions imposed per user from various parties.
+ */
+ private final ArrayMap<IBinder, ClientUserRestrictionState> mOpUserRestrictions =
+ new ArrayMap<>();
+
+ /*
+ * These are app op restrictions imposed globally from various parties within the system.
+ */
+ private final ArrayMap<IBinder, ClientGlobalRestrictionState> mOpGlobalRestrictions =
+ new ArrayMap<>();
+
+ SparseIntArray mProfileOwners;
+
private volatile CheckOpsDelegateDispatcher mCheckOpsDelegateDispatcher =
new CheckOpsDelegateDispatcher(/*policy*/ null, /*delegate*/ null);
+ /**
+ * Reverse lookup for {@link AppOpsManager#opToSwitch(int)}. Initialized once and never
+ * changed
+ */
+ private final SparseArray<int[]> mSwitchedOps = new SparseArray<>();
+
+ private ActivityManagerInternal mActivityManagerInternal;
/** Package sampled for message collection in the current session */
@GuardedBy("this")
@@ -207,8 +382,546 @@ public class AppOpsService extends IAppOpsService.Stub {
/** Package Manager internal. Access via {@link #getPackageManagerInternal()} */
private @Nullable PackageManagerInternal mPackageManagerInternal;
+ /** Interface for app-op modes.*/
+ @VisibleForTesting
+ AppOpsCheckingServiceInterface mAppOpsCheckingService;
+
+ /** Interface for app-op restrictions.*/
+ @VisibleForTesting AppOpsRestrictions mAppOpsRestrictions;
+
+ private AppOpsUidStateTracker mUidStateTracker;
+
+ /** Hands the definition of foreground and uid states */
+ @GuardedBy("this")
+ public AppOpsUidStateTracker getUidStateTracker() {
+ if (mUidStateTracker == null) {
+ mUidStateTracker = new AppOpsUidStateTrackerImpl(
+ LocalServices.getService(ActivityManagerInternal.class),
+ mHandler,
+ r -> {
+ synchronized (AppOpsService.this) {
+ r.run();
+ }
+ },
+ Clock.SYSTEM_CLOCK, mConstants);
+
+ mUidStateTracker.addUidStateChangedCallback(new HandlerExecutor(mHandler),
+ this::onUidStateChanged);
+ }
+ return mUidStateTracker;
+ }
+
+ /**
+ * All times are in milliseconds. These constants are kept synchronized with the system
+ * global Settings. Any access to this class or its fields should be done while
+ * holding the AppOpsService lock.
+ */
+ final class Constants extends ContentObserver {
+
+ /**
+ * How long we want for a drop in uid state from top to settle before applying it.
+ * @see Settings.Global#APP_OPS_CONSTANTS
+ * @see AppOpsManager#KEY_TOP_STATE_SETTLE_TIME
+ */
+ public long TOP_STATE_SETTLE_TIME;
+
+ /**
+ * How long we want for a drop in uid state from foreground to settle before applying it.
+ * @see Settings.Global#APP_OPS_CONSTANTS
+ * @see AppOpsManager#KEY_FG_SERVICE_STATE_SETTLE_TIME
+ */
+ public long FG_SERVICE_STATE_SETTLE_TIME;
+
+ /**
+ * How long we want for a drop in uid state from background to settle before applying it.
+ * @see Settings.Global#APP_OPS_CONSTANTS
+ * @see AppOpsManager#KEY_BG_STATE_SETTLE_TIME
+ */
+ public long BG_STATE_SETTLE_TIME;
+
+ private final KeyValueListParser mParser = new KeyValueListParser(',');
+ private ContentResolver mResolver;
+
+ public Constants(Handler handler) {
+ super(handler);
+ updateConstants();
+ }
+
+ public void startMonitoring(ContentResolver resolver) {
+ mResolver = resolver;
+ mResolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.APP_OPS_CONSTANTS),
+ false, this);
+ updateConstants();
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ updateConstants();
+ }
+
+ private void updateConstants() {
+ String value = mResolver != null ? Settings.Global.getString(mResolver,
+ Settings.Global.APP_OPS_CONSTANTS) : "";
+
+ synchronized (AppOpsService.this) {
+ try {
+ mParser.setString(value);
+ } catch (IllegalArgumentException e) {
+ // Failed to parse the settings string, log this and move on
+ // with defaults.
+ Slog.e(TAG, "Bad app ops settings", e);
+ }
+ TOP_STATE_SETTLE_TIME = mParser.getDurationMillis(
+ KEY_TOP_STATE_SETTLE_TIME, 5 * 1000L);
+ FG_SERVICE_STATE_SETTLE_TIME = mParser.getDurationMillis(
+ KEY_FG_SERVICE_STATE_SETTLE_TIME, 5 * 1000L);
+ BG_STATE_SETTLE_TIME = mParser.getDurationMillis(
+ KEY_BG_STATE_SETTLE_TIME, 1 * 1000L);
+ }
+ }
+
+ void dump(PrintWriter pw) {
+ pw.println(" Settings:");
+
+ pw.print(" "); pw.print(KEY_TOP_STATE_SETTLE_TIME); pw.print("=");
+ TimeUtils.formatDuration(TOP_STATE_SETTLE_TIME, pw);
+ pw.println();
+ pw.print(" "); pw.print(KEY_FG_SERVICE_STATE_SETTLE_TIME); pw.print("=");
+ TimeUtils.formatDuration(FG_SERVICE_STATE_SETTLE_TIME, pw);
+ pw.println();
+ pw.print(" "); pw.print(KEY_BG_STATE_SETTLE_TIME); pw.print("=");
+ TimeUtils.formatDuration(BG_STATE_SETTLE_TIME, pw);
+ pw.println();
+ }
+ }
+
+ @VisibleForTesting
+ final Constants mConstants;
+
+ @VisibleForTesting
+ final class UidState {
+ public final int uid;
+
+ public ArrayMap<String, Ops> pkgOps;
+
+ // true indicates there is an interested observer, false there isn't but it has such an op
+ //TODO: Move foregroundOps and hasForegroundWatchers into the AppOpsServiceInterface.
+ public SparseBooleanArray foregroundOps;
+ public boolean hasForegroundWatchers;
+
+ public UidState(int uid) {
+ this.uid = uid;
+ }
+
+ public void clear() {
+ mAppOpsCheckingService.removeUid(uid);
+ if (pkgOps != null) {
+ for (String packageName : pkgOps.keySet()) {
+ mAppOpsCheckingService.removePackage(packageName, UserHandle.getUserId(uid));
+ }
+ }
+ pkgOps = null;
+ }
+
+ public boolean isDefault() {
+ boolean areAllPackageModesDefault = true;
+ if (pkgOps != null) {
+ for (String packageName : pkgOps.keySet()) {
+ if (!mAppOpsCheckingService.arePackageModesDefault(packageName,
+ UserHandle.getUserId(uid))) {
+ areAllPackageModesDefault = false;
+ break;
+ }
+ }
+ }
+ return (pkgOps == null || pkgOps.isEmpty())
+ && mAppOpsCheckingService.areUidModesDefault(uid)
+ && areAllPackageModesDefault;
+ }
+
+ // Functions for uid mode access and manipulation.
+ public SparseIntArray getNonDefaultUidModes() {
+ return mAppOpsCheckingService.getNonDefaultUidModes(uid);
+ }
+
+ public int getUidMode(int op) {
+ return mAppOpsCheckingService.getUidMode(uid, op);
+ }
+
+ public boolean setUidMode(int op, int mode) {
+ return mAppOpsCheckingService.setUidMode(uid, op, mode);
+ }
+
+ @SuppressWarnings("GuardedBy")
+ int evalMode(int op, int mode) {
+ return getUidStateTracker().evalMode(uid, op, mode);
+ }
+
+ public void evalForegroundOps() {
+ foregroundOps = null;
+ foregroundOps = mAppOpsCheckingService.evalForegroundUidOps(uid, foregroundOps);
+ if (pkgOps != null) {
+ for (int i = pkgOps.size() - 1; i >= 0; i--) {
+ foregroundOps = mAppOpsCheckingService
+ .evalForegroundPackageOps(pkgOps.valueAt(i).packageName, foregroundOps,
+ UserHandle.getUserId(uid));
+ }
+ }
+ hasForegroundWatchers = false;
+ if (foregroundOps != null) {
+ for (int i = 0; i < foregroundOps.size(); i++) {
+ if (foregroundOps.valueAt(i)) {
+ hasForegroundWatchers = true;
+ break;
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("GuardedBy")
+ public int getState() {
+ return getUidStateTracker().getUidState(uid);
+ }
+
+ @SuppressWarnings("GuardedBy")
+ public void dump(PrintWriter pw, long nowElapsed) {
+ getUidStateTracker().dumpUidState(pw, uid, nowElapsed);
+ }
+ }
+
+ final static class Ops extends SparseArray<Op> {
+ final String packageName;
+ final UidState uidState;
+
+ /**
+ * The restriction properties of the package. If {@code null} it could not have been read
+ * yet and has to be refreshed.
+ */
+ @Nullable RestrictionBypass bypass;
+
+ /** Lazily populated cache of attributionTags of this package */
+ final @NonNull ArraySet<String> knownAttributionTags = new ArraySet<>();
+
+ /**
+ * Lazily populated cache of <b>valid</b> attributionTags of this package, a set smaller
+ * than or equal to {@link #knownAttributionTags}.
+ */
+ final @NonNull ArraySet<String> validAttributionTags = new ArraySet<>();
+
+ Ops(String _packageName, UidState _uidState) {
+ packageName = _packageName;
+ uidState = _uidState;
+ }
+ }
+
+ /** Returned from {@link #verifyAndGetBypass(int, String, String, String)}. */
+ private static final class PackageVerificationResult {
+
+ final RestrictionBypass bypass;
+ final boolean isAttributionTagValid;
+
+ PackageVerificationResult(RestrictionBypass bypass, boolean isAttributionTagValid) {
+ this.bypass = bypass;
+ this.isAttributionTagValid = isAttributionTagValid;
+ }
+ }
+
+ final class Op {
+ int op;
+ int uid;
+ final UidState uidState;
+ final @NonNull String packageName;
+
+ /** attributionTag -> AttributedOp */
+ final ArrayMap<String, AttributedOp> mAttributions = new ArrayMap<>(1);
+
+ Op(UidState uidState, String packageName, int op, int uid) {
+ this.op = op;
+ this.uid = uid;
+ this.uidState = uidState;
+ this.packageName = packageName;
+ }
+
+ @Mode int getMode() {
+ return mAppOpsCheckingService.getPackageMode(packageName, this.op,
+ UserHandle.getUserId(this.uid));
+ }
+ void setMode(@Mode int mode) {
+ mAppOpsCheckingService.setPackageMode(packageName, this.op, mode,
+ UserHandle.getUserId(this.uid));
+ }
+
+ void removeAttributionsWithNoTime() {
+ for (int i = mAttributions.size() - 1; i >= 0; i--) {
+ if (!mAttributions.valueAt(i).hasAnyTime()) {
+ mAttributions.removeAt(i);
+ }
+ }
+ }
+
+ private @NonNull AttributedOp getOrCreateAttribution(@NonNull Op parent,
+ @Nullable String attributionTag) {
+ AttributedOp attributedOp;
+
+ attributedOp = mAttributions.get(attributionTag);
+ if (attributedOp == null) {
+ attributedOp = new AttributedOp(AppOpsService.this, attributionTag, parent);
+ mAttributions.put(attributionTag, attributedOp);
+ }
+
+ return attributedOp;
+ }
+
+ @NonNull OpEntry createEntryLocked() {
+ final int numAttributions = mAttributions.size();
+
+ final ArrayMap<String, AppOpsManager.AttributedOpEntry> attributionEntries =
+ new ArrayMap<>(numAttributions);
+ for (int i = 0; i < numAttributions; i++) {
+ attributionEntries.put(mAttributions.keyAt(i),
+ mAttributions.valueAt(i).createAttributedOpEntryLocked());
+ }
+
+ return new OpEntry(op, getMode(), attributionEntries);
+ }
+
+ @NonNull OpEntry createSingleAttributionEntryLocked(@Nullable String attributionTag) {
+ final int numAttributions = mAttributions.size();
+
+ final ArrayMap<String, AttributedOpEntry> attributionEntries = new ArrayMap<>(1);
+ for (int i = 0; i < numAttributions; i++) {
+ if (Objects.equals(mAttributions.keyAt(i), attributionTag)) {
+ attributionEntries.put(mAttributions.keyAt(i),
+ mAttributions.valueAt(i).createAttributedOpEntryLocked());
+ break;
+ }
+ }
+
+ return new OpEntry(op, getMode(), attributionEntries);
+ }
+
+ boolean isRunning() {
+ final int numAttributions = mAttributions.size();
+ for (int i = 0; i < numAttributions; i++) {
+ if (mAttributions.valueAt(i).isRunning()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+
+ final ArrayMap<IBinder, ModeCallback> mModeWatchers = new ArrayMap<>();
+ final ArrayMap<IBinder, SparseArray<ActiveCallback>> mActiveWatchers = new ArrayMap<>();
+ final ArrayMap<IBinder, SparseArray<StartedCallback>> mStartedWatchers = new ArrayMap<>();
+ final ArrayMap<IBinder, SparseArray<NotedCallback>> mNotedWatchers = new ArrayMap<>();
final AudioRestrictionManager mAudioRestrictionManager = new AudioRestrictionManager();
+ final class ModeCallback extends OnOpModeChangedListener implements DeathRecipient {
+ /** If mWatchedOpCode==ALL_OPS notify for ops affected by the switch-op */
+ public static final int ALL_OPS = -2;
+
+ // Need to keep this only because stopWatchingMode needs an IAppOpsCallback.
+ // Otherwise we can just use the IBinder object.
+ private final IAppOpsCallback mCallback;
+
+ ModeCallback(IAppOpsCallback callback, int watchingUid, int flags, int watchedOpCode,
+ int callingUid, int callingPid) {
+ super(watchingUid, flags, watchedOpCode, callingUid, callingPid);
+ this.mCallback = callback;
+ try {
+ mCallback.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ /*ignored*/
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("ModeCallback{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(" watchinguid=");
+ UserHandle.formatUid(sb, getWatchingUid());
+ sb.append(" flags=0x");
+ sb.append(Integer.toHexString(getFlags()));
+ switch (getWatchedOpCode()) {
+ case OP_NONE:
+ break;
+ case ALL_OPS:
+ sb.append(" op=(all)");
+ break;
+ default:
+ sb.append(" op=");
+ sb.append(opToName(getWatchedOpCode()));
+ break;
+ }
+ sb.append(" from uid=");
+ UserHandle.formatUid(sb, getCallingUid());
+ sb.append(" pid=");
+ sb.append(getCallingPid());
+ sb.append('}');
+ return sb.toString();
+ }
+
+ void unlinkToDeath() {
+ mCallback.asBinder().unlinkToDeath(this, 0);
+ }
+
+ @Override
+ public void binderDied() {
+ stopWatchingMode(mCallback);
+ }
+
+ @Override
+ public void onOpModeChanged(int op, int uid, String packageName) throws RemoteException {
+ mCallback.opChanged(op, uid, packageName);
+ }
+ }
+
+ final class ActiveCallback implements DeathRecipient {
+ final IAppOpsActiveCallback mCallback;
+ final int mWatchingUid;
+ final int mCallingUid;
+ final int mCallingPid;
+
+ ActiveCallback(IAppOpsActiveCallback callback, int watchingUid, int callingUid,
+ int callingPid) {
+ mCallback = callback;
+ mWatchingUid = watchingUid;
+ mCallingUid = callingUid;
+ mCallingPid = callingPid;
+ try {
+ mCallback.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ /*ignored*/
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("ActiveCallback{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(" watchinguid=");
+ UserHandle.formatUid(sb, mWatchingUid);
+ sb.append(" from uid=");
+ UserHandle.formatUid(sb, mCallingUid);
+ sb.append(" pid=");
+ sb.append(mCallingPid);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ void destroy() {
+ mCallback.asBinder().unlinkToDeath(this, 0);
+ }
+
+ @Override
+ public void binderDied() {
+ stopWatchingActive(mCallback);
+ }
+ }
+
+ final class StartedCallback implements DeathRecipient {
+ final IAppOpsStartedCallback mCallback;
+ final int mWatchingUid;
+ final int mCallingUid;
+ final int mCallingPid;
+
+ StartedCallback(IAppOpsStartedCallback callback, int watchingUid, int callingUid,
+ int callingPid) {
+ mCallback = callback;
+ mWatchingUid = watchingUid;
+ mCallingUid = callingUid;
+ mCallingPid = callingPid;
+ try {
+ mCallback.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ /*ignored*/
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("StartedCallback{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(" watchinguid=");
+ UserHandle.formatUid(sb, mWatchingUid);
+ sb.append(" from uid=");
+ UserHandle.formatUid(sb, mCallingUid);
+ sb.append(" pid=");
+ sb.append(mCallingPid);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ void destroy() {
+ mCallback.asBinder().unlinkToDeath(this, 0);
+ }
+
+ @Override
+ public void binderDied() {
+ stopWatchingStarted(mCallback);
+ }
+ }
+
+ final class NotedCallback implements DeathRecipient {
+ final IAppOpsNotedCallback mCallback;
+ final int mWatchingUid;
+ final int mCallingUid;
+ final int mCallingPid;
+
+ NotedCallback(IAppOpsNotedCallback callback, int watchingUid, int callingUid,
+ int callingPid) {
+ mCallback = callback;
+ mWatchingUid = watchingUid;
+ mCallingUid = callingUid;
+ mCallingPid = callingPid;
+ try {
+ mCallback.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ /*ignored*/
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("NotedCallback{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(" watchinguid=");
+ UserHandle.formatUid(sb, mWatchingUid);
+ sb.append(" from uid=");
+ UserHandle.formatUid(sb, mCallingUid);
+ sb.append(" pid=");
+ sb.append(mCallingPid);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ void destroy() {
+ mCallback.asBinder().unlinkToDeath(this, 0);
+ }
+
+ @Override
+ public void binderDied() {
+ stopWatchingNoted(mCallback);
+ }
+ }
+
+ /**
+ * Call {@link AttributedOp#onClientDeath attributedOp.onClientDeath(clientId)}.
+ */
+ static void onClientDeath(@NonNull AttributedOp attributedOp,
+ @NonNull IBinder clientId) {
+ attributedOp.onClientDeath(clientId);
+ }
+
+
/**
* Loads the OpsValidation file results into a hashmap {@link #mNoteOpCallerStacktraces}
* so that we do not log the same operation twice between instances
@@ -233,12 +946,20 @@ public class AppOpsService extends IAppOpsService.Stub {
}
public AppOpsService(File storagePath, Handler handler, Context context) {
- this(handler, context, new AppOpsServiceImpl(storagePath, handler, context));
- }
+ mContext = context;
- @VisibleForTesting
- public AppOpsService(Handler handler, Context context,
- AppOpsServiceInterface appOpsServiceInterface) {
+ for (int switchedCode = 0; switchedCode < _NUM_OP; switchedCode++) {
+ int switchCode = AppOpsManager.opToSwitch(switchedCode);
+ mSwitchedOps.put(switchCode,
+ ArrayUtils.appendInt(mSwitchedOps.get(switchCode), switchedCode));
+ }
+ mAppOpsCheckingService =
+ new AppOpsCheckingServiceImpl(this, this, handler, context, mSwitchedOps);
+ mAppOpsRestrictions = new AppOpsRestrictionsImpl(context, handler,
+ mAppOpsCheckingService);
+
+ LockGuard.installLock(this, LockGuard.INDEX_APP_OPS);
+ mFile = new AtomicFile(storagePath, "appops");
if (AppOpsManager.NOTE_OP_COLLECTION_ENABLED) {
mNoteOpCallerStacktracesFile = new File(SystemServiceManager.ensureSystemDir(),
"noteOpStackTraces.json");
@@ -246,25 +967,189 @@ public class AppOpsService extends IAppOpsService.Stub {
} else {
mNoteOpCallerStacktracesFile = null;
}
-
- mAppOpsService = appOpsServiceInterface;
- mContext = context;
mHandler = handler;
+ mConstants = new Constants(mHandler);
+ readState();
}
- /**
- * Publishes binder and local service.
- */
public void publish() {
ServiceManager.addService(Context.APP_OPS_SERVICE, asBinder());
LocalServices.addService(AppOpsManagerInternal.class, mAppOpsManagerInternal);
}
- /**
- * Finishes boot sequence.
- */
+ /** Handler for work when packages are removed or updated */
+ private BroadcastReceiver mOnPackageUpdatedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ String pkgName = intent.getData().getEncodedSchemeSpecificPart();
+ int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID);
+
+ if (action.equals(ACTION_PACKAGE_REMOVED) && !intent.hasExtra(EXTRA_REPLACING)) {
+ synchronized (AppOpsService.this) {
+ UidState uidState = mUidStates.get(uid);
+ if (uidState == null || uidState.pkgOps == null) {
+ return;
+ }
+ mAppOpsCheckingService.removePackage(pkgName, UserHandle.getUserId(uid));
+ Ops removedOps = uidState.pkgOps.remove(pkgName);
+ if (removedOps != null) {
+ scheduleFastWriteLocked();
+ }
+ }
+ } else if (action.equals(Intent.ACTION_PACKAGE_REPLACED)) {
+ AndroidPackage pkg = getPackageManagerInternal().getPackage(pkgName);
+ if (pkg == null) {
+ return;
+ }
+
+ ArrayMap<String, String> dstAttributionTags = new ArrayMap<>();
+ ArraySet<String> attributionTags = new ArraySet<>();
+ attributionTags.add(null);
+ if (pkg.getAttributions() != null) {
+ int numAttributions = pkg.getAttributions().size();
+ for (int attributionNum = 0; attributionNum < numAttributions;
+ attributionNum++) {
+ ParsedAttribution attribution = pkg.getAttributions().get(attributionNum);
+ attributionTags.add(attribution.getTag());
+
+ int numInheritFrom = attribution.getInheritFrom().size();
+ for (int inheritFromNum = 0; inheritFromNum < numInheritFrom;
+ inheritFromNum++) {
+ dstAttributionTags.put(attribution.getInheritFrom().get(inheritFromNum),
+ attribution.getTag());
+ }
+ }
+ }
+
+ synchronized (AppOpsService.this) {
+ UidState uidState = mUidStates.get(uid);
+ if (uidState == null || uidState.pkgOps == null) {
+ return;
+ }
+
+ Ops ops = uidState.pkgOps.get(pkgName);
+ if (ops == null) {
+ return;
+ }
+
+ // Reset cached package properties to re-initialize when needed
+ ops.bypass = null;
+ ops.knownAttributionTags.clear();
+
+ // Merge data collected for removed attributions into their successor
+ // attributions
+ int numOps = ops.size();
+ for (int opNum = 0; opNum < numOps; opNum++) {
+ Op op = ops.valueAt(opNum);
+
+ int numAttributions = op.mAttributions.size();
+ for (int attributionNum = numAttributions - 1; attributionNum >= 0;
+ attributionNum--) {
+ String attributionTag = op.mAttributions.keyAt(attributionNum);
+
+ if (attributionTags.contains(attributionTag)) {
+ // attribution still exist after upgrade
+ continue;
+ }
+
+ String newAttributionTag = dstAttributionTags.get(attributionTag);
+
+ AttributedOp newAttributedOp = op.getOrCreateAttribution(op,
+ newAttributionTag);
+ newAttributedOp.add(op.mAttributions.valueAt(attributionNum));
+ op.mAttributions.removeAt(attributionNum);
+
+ scheduleFastWriteLocked();
+ }
+ }
+ }
+ }
+ }
+ };
+
public void systemReady() {
- mAppOpsService.systemReady();
+ synchronized (this) {
+ upgradeLocked(mVersionAtBoot);
+ }
+
+ mConstants.startMonitoring(mContext.getContentResolver());
+ mHistoricalRegistry.systemReady(mContext.getContentResolver());
+
+ IntentFilter packageUpdateFilter = new IntentFilter();
+ packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+ packageUpdateFilter.addDataScheme("package");
+
+ mContext.registerReceiverAsUser(mOnPackageUpdatedReceiver, UserHandle.ALL,
+ packageUpdateFilter, null, null);
+
+ synchronized (this) {
+ for (int uidNum = mUidStates.size() - 1; uidNum >= 0; uidNum--) {
+ int uid = mUidStates.keyAt(uidNum);
+ UidState uidState = mUidStates.valueAt(uidNum);
+
+ String[] pkgsInUid = getPackagesForUid(uidState.uid);
+ if (ArrayUtils.isEmpty(pkgsInUid)) {
+ uidState.clear();
+ mUidStates.removeAt(uidNum);
+ scheduleFastWriteLocked();
+ continue;
+ }
+
+ ArrayMap<String, Ops> pkgs = uidState.pkgOps;
+ if (pkgs == null) {
+ continue;
+ }
+
+ int numPkgs = pkgs.size();
+ for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
+ String pkg = pkgs.keyAt(pkgNum);
+
+ String action;
+ if (!ArrayUtils.contains(pkgsInUid, pkg)) {
+ action = Intent.ACTION_PACKAGE_REMOVED;
+ } else {
+ action = Intent.ACTION_PACKAGE_REPLACED;
+ }
+
+ SystemServerInitThreadPool.submit(
+ () -> mOnPackageUpdatedReceiver.onReceive(mContext, new Intent(action)
+ .setData(Uri.fromParts("package", pkg, null))
+ .putExtra(Intent.EXTRA_UID, uid)),
+ "Update app-ops uidState in case package " + pkg + " changed");
+ }
+ }
+ }
+
+ final IntentFilter packageSuspendFilter = new IntentFilter();
+ packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
+ packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
+ mContext.registerReceiverAsUser(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final int[] changedUids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
+ final String[] changedPkgs = intent.getStringArrayExtra(
+ Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ for (int code : OPS_RESTRICTED_ON_SUSPEND) {
+ ArraySet<OnOpModeChangedListener> onModeChangedListeners;
+ synchronized (AppOpsService.this) {
+ onModeChangedListeners =
+ mAppOpsCheckingService.getOpModeChangedListeners(code);
+ if (onModeChangedListeners == null) {
+ continue;
+ }
+ }
+ for (int i = 0; i < changedUids.length; i++) {
+ final int changedUid = changedUids[i];
+ final String changedPkg = changedPkgs[i];
+ // We trust packagemanager to insert matching uid and packageNames in the
+ // extras
+ notifyOpChanged(onModeChangedListeners, code, changedUid, changedPkg);
+ }
+ }
+ }
+ }, UserHandle.ALL, packageSuspendFilter, null, null);
final IntentFilter packageAddedFilter = new IntentFilter();
packageAddedFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
@@ -272,8 +1157,9 @@ public class AppOpsService extends IAppOpsService.Stub {
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
+ final Uri data = intent.getData();
- final String packageName = intent.getData().getSchemeSpecificPart();
+ final String packageName = data.getSchemeSpecificPart();
PackageInfo pi = getPackageManagerInternal().getPackageInfo(packageName,
PackageManager.GET_PERMISSIONS, Process.myUid(), mContext.getUserId());
if (isSamplingTarget(pi)) {
@@ -308,6 +1194,8 @@ public class AppOpsService extends IAppOpsService.Stub {
}
}
});
+
+ mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
}
/**
@@ -322,18 +1210,132 @@ public class AppOpsService extends IAppOpsService.Stub {
mCheckOpsDelegateDispatcher = new CheckOpsDelegateDispatcher(policy, delegate);
}
- /**
- * Notify when a package is removed
- */
public void packageRemoved(int uid, String packageName) {
- mAppOpsService.packageRemoved(uid, packageName);
+ synchronized (this) {
+ UidState uidState = mUidStates.get(uid);
+ if (uidState == null) {
+ return;
+ }
+
+ Ops removedOps = null;
+
+ // Remove any package state if such.
+ if (uidState.pkgOps != null) {
+ removedOps = uidState.pkgOps.remove(packageName);
+ mAppOpsCheckingService.removePackage(packageName, UserHandle.getUserId(uid));
+ }
+
+ // If we just nuked the last package state check if the UID is valid.
+ if (removedOps != null && uidState.pkgOps.isEmpty()
+ && getPackagesForUid(uid).length <= 0) {
+ uidState.clear();
+ mUidStates.remove(uid);
+ }
+
+ if (removedOps != null) {
+ scheduleFastWriteLocked();
+
+ final int numOps = removedOps.size();
+ for (int opNum = 0; opNum < numOps; opNum++) {
+ final Op op = removedOps.valueAt(opNum);
+
+ final int numAttributions = op.mAttributions.size();
+ for (int attributionNum = 0; attributionNum < numAttributions;
+ attributionNum++) {
+ AttributedOp attributedOp = op.mAttributions.valueAt(attributionNum);
+
+ while (attributedOp.isRunning()) {
+ attributedOp.finished(attributedOp.mInProgressEvents.keyAt(0));
+ }
+ while (attributedOp.isPaused()) {
+ attributedOp.finished(attributedOp.mPausedInProgressEvents.keyAt(0));
+ }
+ }
+ }
+ }
+ }
+
+ mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::clearHistory,
+ mHistoricalRegistry, uid, packageName));
}
- /**
- * Notify when a uid is removed.
- */
public void uidRemoved(int uid) {
- mAppOpsService.uidRemoved(uid);
+ synchronized (this) {
+ if (mUidStates.indexOfKey(uid) >= 0) {
+ mUidStates.get(uid).clear();
+ mUidStates.remove(uid);
+ scheduleFastWriteLocked();
+ }
+ }
+ }
+
+ // The callback method from ForegroundPolicyInterface
+ private void onUidStateChanged(int uid, int state, boolean foregroundModeMayChange) {
+ synchronized (this) {
+ UidState uidState = getUidStateLocked(uid, true);
+
+ if (uidState != null && foregroundModeMayChange && uidState.hasForegroundWatchers) {
+ for (int fgi = uidState.foregroundOps.size() - 1; fgi >= 0; fgi--) {
+ if (!uidState.foregroundOps.valueAt(fgi)) {
+ continue;
+ }
+ final int code = uidState.foregroundOps.keyAt(fgi);
+
+ if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)
+ && uidState.getUidMode(code) == AppOpsManager.MODE_FOREGROUND) {
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsService::notifyOpChangedForAllPkgsInUid,
+ this, code, uidState.uid, true, null));
+ } else if (uidState.pkgOps != null) {
+ final ArraySet<OnOpModeChangedListener> listenerSet =
+ mAppOpsCheckingService.getOpModeChangedListeners(code);
+ if (listenerSet != null) {
+ for (int cbi = listenerSet.size() - 1; cbi >= 0; cbi--) {
+ final OnOpModeChangedListener listener = listenerSet.valueAt(cbi);
+ if ((listener.getFlags()
+ & AppOpsManager.WATCH_FOREGROUND_CHANGES) == 0
+ || !listener.isWatchingUid(uidState.uid)) {
+ continue;
+ }
+ for (int pkgi = uidState.pkgOps.size() - 1; pkgi >= 0; pkgi--) {
+ final Op op = uidState.pkgOps.valueAt(pkgi).get(code);
+ if (op == null) {
+ continue;
+ }
+ if (op.getMode() == AppOpsManager.MODE_FOREGROUND) {
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsService::notifyOpChanged,
+ this, listenerSet.valueAt(cbi), code, uidState.uid,
+ uidState.pkgOps.keyAt(pkgi)));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (uidState != null && uidState.pkgOps != null) {
+ int numPkgs = uidState.pkgOps.size();
+ for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
+ Ops ops = uidState.pkgOps.valueAt(pkgNum);
+
+ int numOps = ops.size();
+ for (int opNum = 0; opNum < numOps; opNum++) {
+ Op op = ops.valueAt(opNum);
+
+ int numAttributions = op.mAttributions.size();
+ for (int attributionNum = 0; attributionNum < numAttributions;
+ attributionNum++) {
+ AttributedOp attributedOp = op.mAttributions.valueAt(
+ attributionNum);
+
+ attributedOp.onUidStateChanged(state);
+ }
+ }
+ }
+ }
+ }
}
/**
@@ -341,60 +1343,542 @@ public class AppOpsService extends IAppOpsService.Stub {
*/
public void updateUidProcState(int uid, int procState,
@ActivityManager.ProcessCapability int capability) {
- mAppOpsService.updateUidProcState(uid, procState, capability);
+ synchronized (this) {
+ getUidStateTracker().updateUidProcState(uid, procState, capability);
+ if (!mUidStates.contains(uid)) {
+ UidState uidState = new UidState(uid);
+ mUidStates.put(uid, uidState);
+ onUidStateChanged(uid,
+ AppOpsUidStateTracker.processStateToUidState(procState), false);
+ }
+ }
}
- /**
- * Initiates shutdown.
- */
public void shutdown() {
- mAppOpsService.shutdown();
-
+ Slog.w(TAG, "Writing app ops before shutdown...");
+ boolean doWrite = false;
+ synchronized (this) {
+ if (mWriteScheduled) {
+ mWriteScheduled = false;
+ mFastWriteScheduled = false;
+ mHandler.removeCallbacks(mWriteRunner);
+ doWrite = true;
+ }
+ }
+ if (doWrite) {
+ writeState();
+ }
if (AppOpsManager.NOTE_OP_COLLECTION_ENABLED && mWriteNoteOpsScheduled) {
writeNoteOps();
}
+
+ mHistoricalRegistry.shutdown();
+ }
+
+ private ArrayList<AppOpsManager.OpEntry> collectOps(Ops pkgOps, int[] ops) {
+ ArrayList<AppOpsManager.OpEntry> resOps = null;
+ if (ops == null) {
+ resOps = new ArrayList<>();
+ for (int j=0; j<pkgOps.size(); j++) {
+ Op curOp = pkgOps.valueAt(j);
+ resOps.add(getOpEntryForResult(curOp));
+ }
+ } else {
+ for (int j=0; j<ops.length; j++) {
+ Op curOp = pkgOps.get(ops[j]);
+ if (curOp != null) {
+ if (resOps == null) {
+ resOps = new ArrayList<>();
+ }
+ resOps.add(getOpEntryForResult(curOp));
+ }
+ }
+ }
+ return resOps;
+ }
+
+ @Nullable
+ private ArrayList<AppOpsManager.OpEntry> collectUidOps(@NonNull UidState uidState,
+ @Nullable int[] ops) {
+ final SparseIntArray opModes = uidState.getNonDefaultUidModes();
+ if (opModes == null) {
+ return null;
+ }
+
+ int opModeCount = opModes.size();
+ if (opModeCount == 0) {
+ return null;
+ }
+ ArrayList<AppOpsManager.OpEntry> resOps = null;
+ if (ops == null) {
+ resOps = new ArrayList<>();
+ for (int i = 0; i < opModeCount; i++) {
+ int code = opModes.keyAt(i);
+ resOps.add(new OpEntry(code, opModes.get(code), Collections.emptyMap()));
+ }
+ } else {
+ for (int j=0; j<ops.length; j++) {
+ int code = ops[j];
+ if (opModes.indexOfKey(code) >= 0) {
+ if (resOps == null) {
+ resOps = new ArrayList<>();
+ }
+ resOps.add(new OpEntry(code, opModes.get(code), Collections.emptyMap()));
+ }
+ }
+ }
+ return resOps;
+ }
+
+ private static @NonNull OpEntry getOpEntryForResult(@NonNull Op op) {
+ return op.createEntryLocked();
}
@Override
public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) {
- return mAppOpsService.getPackagesForOps(ops);
+ final int callingUid = Binder.getCallingUid();
+ final boolean hasAllPackageAccess = mContext.checkPermission(
+ Manifest.permission.GET_APP_OPS_STATS, Binder.getCallingPid(),
+ Binder.getCallingUid(), null) == PackageManager.PERMISSION_GRANTED;
+ ArrayList<AppOpsManager.PackageOps> res = null;
+ synchronized (this) {
+ final int uidStateCount = mUidStates.size();
+ for (int i = 0; i < uidStateCount; i++) {
+ UidState uidState = mUidStates.valueAt(i);
+ if (uidState.pkgOps == null || uidState.pkgOps.isEmpty()) {
+ continue;
+ }
+ ArrayMap<String, Ops> packages = uidState.pkgOps;
+ final int packageCount = packages.size();
+ for (int j = 0; j < packageCount; j++) {
+ Ops pkgOps = packages.valueAt(j);
+ ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
+ if (resOps != null) {
+ if (res == null) {
+ res = new ArrayList<>();
+ }
+ AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
+ pkgOps.packageName, pkgOps.uidState.uid, resOps);
+ // Caller can always see their packages and with a permission all.
+ if (hasAllPackageAccess || callingUid == pkgOps.uidState.uid) {
+ res.add(resPackage);
+ }
+ }
+ }
+ }
+ }
+ return res;
}
@Override
public List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName,
int[] ops) {
- return mAppOpsService.getOpsForPackage(uid, packageName, ops);
+ enforceGetAppOpsStatsPermissionIfNeeded(uid,packageName);
+ String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+ if (resolvedPackageName == null) {
+ return Collections.emptyList();
+ }
+ synchronized (this) {
+ Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, false, null,
+ /* edit */ false);
+ if (pkgOps == null) {
+ return null;
+ }
+ ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
+ if (resOps == null) {
+ return null;
+ }
+ ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>();
+ AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
+ pkgOps.packageName, pkgOps.uidState.uid, resOps);
+ res.add(resPackage);
+ return res;
+ }
+ }
+
+ private void enforceGetAppOpsStatsPermissionIfNeeded(int uid, String packageName) {
+ final int callingUid = Binder.getCallingUid();
+ // We get to access everything
+ if (callingUid == Process.myPid()) {
+ return;
+ }
+ // Apps can access their own data
+ if (uid == callingUid && packageName != null
+ && checkPackage(uid, packageName) == MODE_ALLOWED) {
+ return;
+ }
+ // Otherwise, you need a permission...
+ mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
+ Binder.getCallingPid(), callingUid, null);
+ }
+
+ /**
+ * Verify that historical appop request arguments are valid.
+ */
+ private void ensureHistoricalOpRequestIsValid(int uid, String packageName,
+ String attributionTag, List<String> opNames, int filter, long beginTimeMillis,
+ long endTimeMillis, int flags) {
+ if ((filter & FILTER_BY_UID) != 0) {
+ Preconditions.checkArgument(uid != Process.INVALID_UID);
+ } else {
+ Preconditions.checkArgument(uid == Process.INVALID_UID);
+ }
+
+ if ((filter & FILTER_BY_PACKAGE_NAME) != 0) {
+ Objects.requireNonNull(packageName);
+ } else {
+ Preconditions.checkArgument(packageName == null);
+ }
+
+ if ((filter & FILTER_BY_ATTRIBUTION_TAG) == 0) {
+ Preconditions.checkArgument(attributionTag == null);
+ }
+
+ if ((filter & FILTER_BY_OP_NAMES) != 0) {
+ Objects.requireNonNull(opNames);
+ } else {
+ Preconditions.checkArgument(opNames == null);
+ }
+
+ Preconditions.checkFlagsArgument(filter,
+ FILTER_BY_UID | FILTER_BY_PACKAGE_NAME | FILTER_BY_ATTRIBUTION_TAG
+ | FILTER_BY_OP_NAMES);
+ Preconditions.checkArgumentNonnegative(beginTimeMillis);
+ Preconditions.checkArgument(endTimeMillis > beginTimeMillis);
+ Preconditions.checkFlagsArgument(flags, OP_FLAGS_ALL);
}
@Override
public void getHistoricalOps(int uid, String packageName, String attributionTag,
List<String> opNames, int dataType, int filter, long beginTimeMillis,
long endTimeMillis, int flags, RemoteCallback callback) {
- mAppOpsService.getHistoricalOps(uid, packageName, attributionTag, opNames,
- dataType, filter, beginTimeMillis, endTimeMillis, flags, callback);
+ PackageManager pm = mContext.getPackageManager();
+
+ ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
+ beginTimeMillis, endTimeMillis, flags);
+ Objects.requireNonNull(callback, "callback cannot be null");
+ ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
+ boolean isSelfRequest = (filter & FILTER_BY_UID) != 0 && uid == Binder.getCallingUid();
+ if (!isSelfRequest) {
+ boolean isCallerInstrumented =
+ ami.getInstrumentationSourceUid(Binder.getCallingUid()) != Process.INVALID_UID;
+ boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
+ boolean isCallerPermissionController;
+ try {
+ isCallerPermissionController = pm.getPackageUidAsUser(
+ mContext.getPackageManager().getPermissionControllerPackageName(), 0,
+ UserHandle.getUserId(Binder.getCallingUid()))
+ == Binder.getCallingUid();
+ } catch (PackageManager.NameNotFoundException doesNotHappen) {
+ return;
+ }
+
+ boolean doesCallerHavePermission = mContext.checkPermission(
+ android.Manifest.permission.GET_HISTORICAL_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid())
+ == PackageManager.PERMISSION_GRANTED;
+
+ if (!isCallerSystem && !isCallerInstrumented && !isCallerPermissionController
+ && !doesCallerHavePermission) {
+ mHandler.post(() -> callback.sendResult(new Bundle()));
+ return;
+ }
+
+ mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
+ }
+
+ final String[] opNamesArray = (opNames != null)
+ ? opNames.toArray(new String[opNames.size()]) : null;
+
+ Set<String> attributionChainExemptPackages = null;
+ if ((dataType & HISTORY_FLAG_GET_ATTRIBUTION_CHAINS) != 0) {
+ attributionChainExemptPackages =
+ PermissionManager.getIndicatorExemptedPackages(mContext);
+ }
+
+ final String[] chainExemptPkgArray = attributionChainExemptPackages != null
+ ? attributionChainExemptPackages.toArray(
+ new String[attributionChainExemptPackages.size()]) : null;
+
+ // Must not hold the appops lock
+ mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOps,
+ mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType,
+ filter, beginTimeMillis, endTimeMillis, flags, chainExemptPkgArray,
+ callback).recycleOnUse());
}
@Override
public void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag,
List<String> opNames, int dataType, int filter, long beginTimeMillis,
long endTimeMillis, int flags, RemoteCallback callback) {
- mAppOpsService.getHistoricalOpsFromDiskRaw(uid, packageName, attributionTag,
- opNames, dataType, filter, beginTimeMillis, endTimeMillis, flags, callback);
+ ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
+ beginTimeMillis, endTimeMillis, flags);
+ Objects.requireNonNull(callback, "callback cannot be null");
+
+ mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
+ Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
+
+ final String[] opNamesArray = (opNames != null)
+ ? opNames.toArray(new String[opNames.size()]) : null;
+
+ Set<String> attributionChainExemptPackages = null;
+ if ((dataType & HISTORY_FLAG_GET_ATTRIBUTION_CHAINS) != 0) {
+ attributionChainExemptPackages =
+ PermissionManager.getIndicatorExemptedPackages(mContext);
+ }
+
+ final String[] chainExemptPkgArray = attributionChainExemptPackages != null
+ ? attributionChainExemptPackages.toArray(
+ new String[attributionChainExemptPackages.size()]) : null;
+
+ // Must not hold the appops lock
+ mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOpsFromDiskRaw,
+ mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType,
+ filter, beginTimeMillis, endTimeMillis, flags, chainExemptPkgArray,
+ callback).recycleOnUse());
}
@Override
public void reloadNonHistoricalState() {
- mAppOpsService.reloadNonHistoricalState();
+ mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
+ Binder.getCallingPid(), Binder.getCallingUid(), "reloadNonHistoricalState");
+ writeState();
+ readState();
}
@Override
public List<AppOpsManager.PackageOps> getUidOps(int uid, int[] ops) {
- return mAppOpsService.getUidOps(uid, ops);
+ mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid(), null);
+ synchronized (this) {
+ UidState uidState = getUidStateLocked(uid, false);
+ if (uidState == null) {
+ return null;
+ }
+ ArrayList<AppOpsManager.OpEntry> resOps = collectUidOps(uidState, ops);
+ if (resOps == null) {
+ return null;
+ }
+ ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>();
+ AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
+ null, uidState.uid, resOps);
+ res.add(resPackage);
+ return res;
+ }
+ }
+
+ private void pruneOpLocked(Op op, int uid, String packageName) {
+ op.removeAttributionsWithNoTime();
+
+ if (op.mAttributions.isEmpty()) {
+ Ops ops = getOpsLocked(uid, packageName, null, false, null, /* edit */ false);
+ if (ops != null) {
+ ops.remove(op.op);
+ op.setMode(AppOpsManager.opToDefaultMode(op.op));
+ if (ops.size() <= 0) {
+ UidState uidState = ops.uidState;
+ ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
+ if (pkgOps != null) {
+ pkgOps.remove(ops.packageName);
+ mAppOpsCheckingService.removePackage(ops.packageName,
+ UserHandle.getUserId(uidState.uid));
+ if (pkgOps.isEmpty()) {
+ uidState.pkgOps = null;
+ }
+ if (uidState.isDefault()) {
+ uidState.clear();
+ mUidStates.remove(uid);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void enforceManageAppOpsModes(int callingPid, int callingUid, int targetUid) {
+ if (callingPid == Process.myPid()) {
+ return;
+ }
+ final int callingUser = UserHandle.getUserId(callingUid);
+ synchronized (this) {
+ if (mProfileOwners != null && mProfileOwners.get(callingUser, -1) == callingUid) {
+ if (targetUid >= 0 && callingUser == UserHandle.getUserId(targetUid)) {
+ // Profile owners are allowed to change modes but only for apps
+ // within their user.
+ return;
+ }
+ }
+ }
+ mContext.enforcePermission(android.Manifest.permission.MANAGE_APP_OPS_MODES,
+ Binder.getCallingPid(), Binder.getCallingUid(), null);
}
@Override
public void setUidMode(int code, int uid, int mode) {
- mAppOpsService.setUidMode(code, uid, mode, null);
+ setUidMode(code, uid, mode, null);
+ }
+
+ private void setUidMode(int code, int uid, int mode,
+ @Nullable IAppOpsCallback permissionPolicyCallback) {
+ if (DEBUG) {
+ Slog.i(TAG, "uid " + uid + " OP_" + opToName(code) + " := " + modeToName(mode)
+ + " by uid " + Binder.getCallingUid());
+ }
+
+ enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
+ verifyIncomingOp(code);
+ code = AppOpsManager.opToSwitch(code);
+
+ if (permissionPolicyCallback == null) {
+ updatePermissionRevokedCompat(uid, code, mode);
+ }
+
+ int previousMode;
+ synchronized (this) {
+ final int defaultMode = AppOpsManager.opToDefaultMode(code);
+
+ UidState uidState = getUidStateLocked(uid, false);
+ if (uidState == null) {
+ if (mode == defaultMode) {
+ return;
+ }
+ uidState = new UidState(uid);
+ mUidStates.put(uid, uidState);
+ }
+ if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)) {
+ previousMode = uidState.getUidMode(code);
+ } else {
+ // doesn't look right but is legacy behavior.
+ previousMode = MODE_DEFAULT;
+ }
+
+ if (!uidState.setUidMode(code, mode)) {
+ return;
+ }
+ uidState.evalForegroundOps();
+ if (mode != MODE_ERRORED && mode != previousMode) {
+ updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
+ }
+ }
+
+ notifyOpChangedForAllPkgsInUid(code, uid, false, permissionPolicyCallback);
+ notifyOpChangedSync(code, uid, null, mode, previousMode);
+ }
+
+ /**
+ * Notify that an op changed for all packages in an uid.
+ *
+ * @param code The op that changed
+ * @param uid The uid the op was changed for
+ * @param onlyForeground Only notify watchers that watch for foreground changes
+ */
+ private void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground,
+ @Nullable IAppOpsCallback callbackToIgnore) {
+ ModeCallback listenerToIgnore = callbackToIgnore != null
+ ? mModeWatchers.get(callbackToIgnore.asBinder()) : null;
+ mAppOpsCheckingService.notifyOpChangedForAllPkgsInUid(code, uid, onlyForeground,
+ listenerToIgnore);
+ }
+
+ private void updatePermissionRevokedCompat(int uid, int switchCode, int mode) {
+ PackageManager packageManager = mContext.getPackageManager();
+ if (packageManager == null) {
+ // This can only happen during early boot. At this time the permission state and appop
+ // state are in sync
+ return;
+ }
+
+ String[] packageNames = packageManager.getPackagesForUid(uid);
+ if (ArrayUtils.isEmpty(packageNames)) {
+ return;
+ }
+ String packageName = packageNames[0];
+
+ int[] ops = mSwitchedOps.get(switchCode);
+ for (int code : ops) {
+ String permissionName = AppOpsManager.opToPermission(code);
+ if (permissionName == null) {
+ continue;
+ }
+
+ if (packageManager.checkPermission(permissionName, packageName)
+ != PackageManager.PERMISSION_GRANTED) {
+ continue;
+ }
+
+ PermissionInfo permissionInfo;
+ try {
+ permissionInfo = packageManager.getPermissionInfo(permissionName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ continue;
+ }
+
+ if (!permissionInfo.isRuntime()) {
+ continue;
+ }
+
+ boolean supportsRuntimePermissions = getPackageManagerInternal()
+ .getUidTargetSdkVersion(uid) >= Build.VERSION_CODES.M;
+
+ UserHandle user = UserHandle.getUserHandleForUid(uid);
+ boolean isRevokedCompat;
+ if (permissionInfo.backgroundPermission != null) {
+ if (packageManager.checkPermission(permissionInfo.backgroundPermission, packageName)
+ == PackageManager.PERMISSION_GRANTED) {
+ boolean isBackgroundRevokedCompat = mode != AppOpsManager.MODE_ALLOWED;
+
+ if (isBackgroundRevokedCompat && supportsRuntimePermissions) {
+ Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime"
+ + " permission state, this is discouraged and you should revoke the"
+ + " runtime permission instead: uid=" + uid + ", switchCode="
+ + switchCode + ", mode=" + mode + ", permission="
+ + permissionInfo.backgroundPermission);
+ }
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ packageManager.updatePermissionFlags(permissionInfo.backgroundPermission,
+ packageName, PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
+ isBackgroundRevokedCompat
+ ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED
+ && mode != AppOpsManager.MODE_FOREGROUND;
+ } else {
+ isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED;
+ }
+
+ if (isRevokedCompat && supportsRuntimePermissions) {
+ Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime"
+ + " permission state, this is discouraged and you should revoke the"
+ + " runtime permission instead: uid=" + uid + ", switchCode="
+ + switchCode + ", mode=" + mode + ", permission=" + permissionName);
+ }
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ packageManager.updatePermissionFlags(permissionName, packageName,
+ PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, isRevokedCompat
+ ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ private void notifyOpChangedSync(int code, int uid, @NonNull String packageName, int mode,
+ int previousMode) {
+ final StorageManagerInternal storageManagerInternal =
+ LocalServices.getService(StorageManagerInternal.class);
+ if (storageManagerInternal != null) {
+ storageManagerInternal.onAppOpsChanged(code, uid, packageName, mode, previousMode);
+ }
}
/**
@@ -407,12 +1891,309 @@ public class AppOpsService extends IAppOpsService.Stub {
*/
@Override
public void setMode(int code, int uid, @NonNull String packageName, int mode) {
- mAppOpsService.setMode(code, uid, packageName, mode, null);
+ setMode(code, uid, packageName, mode, null);
+ }
+
+ void setMode(int code, int uid, @NonNull String packageName, int mode,
+ @Nullable IAppOpsCallback permissionPolicyCallback) {
+ enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
+ verifyIncomingOp(code);
+ if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+ return;
+ }
+
+ ArraySet<OnOpModeChangedListener> repCbs = null;
+ code = AppOpsManager.opToSwitch(code);
+
+ PackageVerificationResult pvr;
+ try {
+ pvr = verifyAndGetBypass(uid, packageName, null);
+ } catch (SecurityException e) {
+ Slog.e(TAG, "Cannot setMode", e);
+ return;
+ }
+
+ int previousMode = MODE_DEFAULT;
+ synchronized (this) {
+ UidState uidState = getUidStateLocked(uid, false);
+ Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ true);
+ if (op != null) {
+ if (op.getMode() != mode) {
+ previousMode = op.getMode();
+ op.setMode(mode);
+
+ if (uidState != null) {
+ uidState.evalForegroundOps();
+ }
+ ArraySet<OnOpModeChangedListener> cbs =
+ mAppOpsCheckingService.getOpModeChangedListeners(code);
+ if (cbs != null) {
+ if (repCbs == null) {
+ repCbs = new ArraySet<>();
+ }
+ repCbs.addAll(cbs);
+ }
+ cbs = mAppOpsCheckingService.getPackageModeChangedListeners(packageName);
+ if (cbs != null) {
+ if (repCbs == null) {
+ repCbs = new ArraySet<>();
+ }
+ repCbs.addAll(cbs);
+ }
+ if (repCbs != null && permissionPolicyCallback != null) {
+ repCbs.remove(mModeWatchers.get(permissionPolicyCallback.asBinder()));
+ }
+ if (mode == AppOpsManager.opToDefaultMode(op.op)) {
+ // If going into the default mode, prune this op
+ // if there is nothing else interesting in it.
+ pruneOpLocked(op, uid, packageName);
+ }
+ scheduleFastWriteLocked();
+ if (mode != MODE_ERRORED) {
+ updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
+ }
+ }
+ }
+ }
+ if (repCbs != null) {
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsService::notifyOpChanged,
+ this, repCbs, code, uid, packageName));
+ }
+
+ notifyOpChangedSync(code, uid, packageName, mode, previousMode);
+ }
+
+ private void notifyOpChanged(ArraySet<OnOpModeChangedListener> callbacks, int code,
+ int uid, String packageName) {
+ for (int i = 0; i < callbacks.size(); i++) {
+ final OnOpModeChangedListener callback = callbacks.valueAt(i);
+ notifyOpChanged(callback, code, uid, packageName);
+ }
+ }
+
+ private void notifyOpChanged(OnOpModeChangedListener callback, int code,
+ int uid, String packageName) {
+ mAppOpsCheckingService.notifyOpChanged(callback, code, uid, packageName);
+ }
+
+ private static ArrayList<ChangeRec> addChange(ArrayList<ChangeRec> reports,
+ int op, int uid, String packageName, int previousMode) {
+ boolean duplicate = false;
+ if (reports == null) {
+ reports = new ArrayList<>();
+ } else {
+ final int reportCount = reports.size();
+ for (int j = 0; j < reportCount; j++) {
+ ChangeRec report = reports.get(j);
+ if (report.op == op && report.pkg.equals(packageName)) {
+ duplicate = true;
+ break;
+ }
+ }
+ }
+ if (!duplicate) {
+ reports.add(new ChangeRec(op, uid, packageName, previousMode));
+ }
+
+ return reports;
+ }
+
+ private static HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> addCallbacks(
+ HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks,
+ int op, int uid, String packageName, int previousMode,
+ ArraySet<OnOpModeChangedListener> cbs) {
+ if (cbs == null) {
+ return callbacks;
+ }
+ if (callbacks == null) {
+ callbacks = new HashMap<>();
+ }
+ final int N = cbs.size();
+ for (int i=0; i<N; i++) {
+ OnOpModeChangedListener cb = cbs.valueAt(i);
+ ArrayList<ChangeRec> reports = callbacks.get(cb);
+ ArrayList<ChangeRec> changed = addChange(reports, op, uid, packageName, previousMode);
+ if (changed != reports) {
+ callbacks.put(cb, changed);
+ }
+ }
+ return callbacks;
+ }
+
+ static final class ChangeRec {
+ final int op;
+ final int uid;
+ final String pkg;
+ final int previous_mode;
+
+ ChangeRec(int _op, int _uid, String _pkg, int _previous_mode) {
+ op = _op;
+ uid = _uid;
+ pkg = _pkg;
+ previous_mode = _previous_mode;
+ }
}
@Override
public void resetAllModes(int reqUserId, String reqPackageName) {
- mAppOpsService.resetAllModes(reqUserId, reqPackageName);
+ final int callingPid = Binder.getCallingPid();
+ final int callingUid = Binder.getCallingUid();
+ reqUserId = ActivityManager.handleIncomingUser(callingPid, callingUid, reqUserId,
+ true, true, "resetAllModes", null);
+
+ int reqUid = -1;
+ if (reqPackageName != null) {
+ try {
+ reqUid = AppGlobals.getPackageManager().getPackageUid(
+ reqPackageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, reqUserId);
+ } catch (RemoteException e) {
+ /* ignore - local call */
+ }
+ }
+
+ enforceManageAppOpsModes(callingPid, callingUid, reqUid);
+
+ HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks = null;
+ ArrayList<ChangeRec> allChanges = new ArrayList<>();
+ synchronized (this) {
+ boolean changed = false;
+ for (int i = mUidStates.size() - 1; i >= 0; i--) {
+ UidState uidState = mUidStates.valueAt(i);
+
+ SparseIntArray opModes = uidState.getNonDefaultUidModes();
+ if (opModes != null && (uidState.uid == reqUid || reqUid == -1)) {
+ final int uidOpCount = opModes.size();
+ for (int j = uidOpCount - 1; j >= 0; j--) {
+ final int code = opModes.keyAt(j);
+ if (AppOpsManager.opAllowsReset(code)) {
+ int previousMode = opModes.valueAt(j);
+ uidState.setUidMode(code, AppOpsManager.opToDefaultMode(code));
+ for (String packageName : getPackagesForUid(uidState.uid)) {
+ callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
+ previousMode,
+ mAppOpsCheckingService.getOpModeChangedListeners(code));
+ callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
+ previousMode, mAppOpsCheckingService
+ .getPackageModeChangedListeners(packageName));
+
+ allChanges = addChange(allChanges, code, uidState.uid,
+ packageName, previousMode);
+ }
+ }
+ }
+ }
+
+ if (uidState.pkgOps == null) {
+ continue;
+ }
+
+ if (reqUserId != UserHandle.USER_ALL
+ && reqUserId != UserHandle.getUserId(uidState.uid)) {
+ // Skip any ops for a different user
+ continue;
+ }
+
+ Map<String, Ops> packages = uidState.pkgOps;
+ Iterator<Map.Entry<String, Ops>> it = packages.entrySet().iterator();
+ boolean uidChanged = false;
+ while (it.hasNext()) {
+ Map.Entry<String, Ops> ent = it.next();
+ String packageName = ent.getKey();
+ if (reqPackageName != null && !reqPackageName.equals(packageName)) {
+ // Skip any ops for a different package
+ continue;
+ }
+ Ops pkgOps = ent.getValue();
+ for (int j=pkgOps.size()-1; j>=0; j--) {
+ Op curOp = pkgOps.valueAt(j);
+ if (shouldDeferResetOpToDpm(curOp.op)) {
+ deferResetOpToDpm(curOp.op, reqPackageName, reqUserId);
+ continue;
+ }
+ if (AppOpsManager.opAllowsReset(curOp.op)
+ && curOp.getMode() != AppOpsManager.opToDefaultMode(curOp.op)) {
+ int previousMode = curOp.getMode();
+ curOp.setMode(AppOpsManager.opToDefaultMode(curOp.op));
+ changed = true;
+ uidChanged = true;
+ final int uid = curOp.uidState.uid;
+ callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
+ previousMode,
+ mAppOpsCheckingService.getOpModeChangedListeners(curOp.op));
+ callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
+ previousMode, mAppOpsCheckingService
+ .getPackageModeChangedListeners(packageName));
+
+ allChanges = addChange(allChanges, curOp.op, uid, packageName,
+ previousMode);
+ curOp.removeAttributionsWithNoTime();
+ if (curOp.mAttributions.isEmpty()) {
+ pkgOps.removeAt(j);
+ }
+ }
+ }
+ if (pkgOps.size() == 0) {
+ it.remove();
+ mAppOpsCheckingService.removePackage(packageName,
+ UserHandle.getUserId(uidState.uid));
+ }
+ }
+ if (uidState.isDefault()) {
+ uidState.clear();
+ mUidStates.remove(uidState.uid);
+ }
+ if (uidChanged) {
+ uidState.evalForegroundOps();
+ }
+ }
+
+ if (changed) {
+ scheduleFastWriteLocked();
+ }
+ }
+ if (callbacks != null) {
+ for (Map.Entry<OnOpModeChangedListener, ArrayList<ChangeRec>> ent
+ : callbacks.entrySet()) {
+ OnOpModeChangedListener cb = ent.getKey();
+ ArrayList<ChangeRec> reports = ent.getValue();
+ for (int i=0; i<reports.size(); i++) {
+ ChangeRec rep = reports.get(i);
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsService::notifyOpChanged,
+ this, cb, rep.op, rep.uid, rep.pkg));
+ }
+ }
+ }
+
+ int numChanges = allChanges.size();
+ for (int i = 0; i < numChanges; i++) {
+ ChangeRec change = allChanges.get(i);
+ notifyOpChangedSync(change.op, change.uid, change.pkg,
+ AppOpsManager.opToDefaultMode(change.op), change.previous_mode);
+ }
+ }
+
+ private boolean shouldDeferResetOpToDpm(int op) {
+ // TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission
+ // pre-grants to a role-based mechanism or another general-purpose mechanism.
+ return dpmi != null && dpmi.supportsResetOp(op);
+ }
+
+ /** Assumes {@link #shouldDeferResetOpToDpm(int)} is true. */
+ private void deferResetOpToDpm(int op, String packageName, @UserIdInt int userId) {
+ // TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission
+ // pre-grants to a role-based mechanism or another general-purpose mechanism.
+ dpmi.resetOp(op, packageName, userId);
+ }
+
+ private void evalAllForegroundOpsLocked() {
+ for (int uidi = mUidStates.size() - 1; uidi >= 0; uidi--) {
+ final UidState uidState = mUidStates.valueAt(uidi);
+ if (uidState.foregroundOps != null) {
+ uidState.evalForegroundOps();
+ }
+ }
}
@Override
@@ -423,17 +2204,66 @@ public class AppOpsService extends IAppOpsService.Stub {
@Override
public void startWatchingModeWithFlags(int op, String packageName, int flags,
IAppOpsCallback callback) {
- mAppOpsService.startWatchingModeWithFlags(op, packageName, flags, callback);
+ int watchedUid = -1;
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ // TODO: should have a privileged permission to protect this.
+ // Also, if the caller has requested WATCH_FOREGROUND_CHANGES, should we require
+ // the USAGE_STATS permission since this can provide information about when an
+ // app is in the foreground?
+ Preconditions.checkArgumentInRange(op, AppOpsManager.OP_NONE,
+ AppOpsManager._NUM_OP - 1, "Invalid op code: " + op);
+ if (callback == null) {
+ return;
+ }
+ final boolean mayWatchPackageName = packageName != null
+ && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(callingUid));
+ synchronized (this) {
+ int switchOp = (op != AppOpsManager.OP_NONE) ? AppOpsManager.opToSwitch(op) : op;
+
+ int notifiedOps;
+ if ((flags & CALL_BACK_ON_SWITCHED_OP) == 0) {
+ if (op == OP_NONE) {
+ notifiedOps = ALL_OPS;
+ } else {
+ notifiedOps = op;
+ }
+ } else {
+ notifiedOps = switchOp;
+ }
+
+ ModeCallback cb = mModeWatchers.get(callback.asBinder());
+ if (cb == null) {
+ cb = new ModeCallback(callback, watchedUid, flags, notifiedOps, callingUid,
+ callingPid);
+ mModeWatchers.put(callback.asBinder(), cb);
+ }
+ if (switchOp != AppOpsManager.OP_NONE) {
+ mAppOpsCheckingService.startWatchingOpModeChanged(cb, switchOp);
+ }
+ if (mayWatchPackageName) {
+ mAppOpsCheckingService.startWatchingPackageModeChanged(cb, packageName);
+ }
+ evalAllForegroundOpsLocked();
+ }
}
@Override
public void stopWatchingMode(IAppOpsCallback callback) {
- mAppOpsService.stopWatchingMode(callback);
+ if (callback == null) {
+ return;
+ }
+ synchronized (this) {
+ ModeCallback cb = mModeWatchers.remove(callback.asBinder());
+ if (cb != null) {
+ cb.unlinkToDeath();
+ mAppOpsCheckingService.removeListener(cb);
+ }
+
+ evalAllForegroundOpsLocked();
+ }
}
- /**
- * @return the current {@link CheckOpsDelegate}.
- */
public CheckOpsDelegate getAppOpsServiceDelegate() {
synchronized (AppOpsService.this) {
final CheckOpsDelegateDispatcher dispatcher = mCheckOpsDelegateDispatcher;
@@ -441,9 +2271,6 @@ public class AppOpsService extends IAppOpsService.Stub {
}
}
- /**
- * Sets the appops {@link CheckOpsDelegate}
- */
public void setAppOpsServiceDelegate(CheckOpsDelegate delegate) {
synchronized (AppOpsService.this) {
final CheckOpsDelegateDispatcher oldDispatcher = mCheckOpsDelegateDispatcher;
@@ -467,7 +2294,58 @@ public class AppOpsService extends IAppOpsService.Stub {
private int checkOperationImpl(int code, int uid, String packageName,
@Nullable String attributionTag, boolean raw) {
- return mAppOpsService.checkOperation(code, uid, packageName, attributionTag, raw);
+ verifyIncomingOp(code);
+ if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+ return AppOpsManager.opToDefaultMode(code);
+ }
+
+ String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+ if (resolvedPackageName == null) {
+ return AppOpsManager.MODE_IGNORED;
+ }
+ return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag, raw);
+ }
+
+ /**
+ * Get the mode of an app-op.
+ *
+ * @param code The code of the op
+ * @param uid The uid of the package the op belongs to
+ * @param packageName The package the op belongs to
+ * @param raw If the raw state of eval-ed state should be checked.
+ *
+ * @return The mode of the op
+ */
+ private @Mode int checkOperationUnchecked(int code, int uid, @NonNull String packageName,
+ @Nullable String attributionTag, boolean raw) {
+ PackageVerificationResult pvr;
+ try {
+ pvr = verifyAndGetBypass(uid, packageName, null);
+ } catch (SecurityException e) {
+ Slog.e(TAG, "checkOperation", e);
+ return AppOpsManager.opToDefaultMode(code);
+ }
+
+ if (isOpRestrictedDueToSuspend(code, packageName, uid)) {
+ return AppOpsManager.MODE_IGNORED;
+ }
+ synchronized (this) {
+ if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, true)) {
+ return AppOpsManager.MODE_IGNORED;
+ }
+ code = AppOpsManager.opToSwitch(code);
+ UidState uidState = getUidStateLocked(uid, false);
+ if (uidState != null
+ && uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)) {
+ final int rawMode = uidState.getUidMode(code);
+ return raw ? rawMode : uidState.evalMode(code, rawMode);
+ }
+ Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ false);
+ if (op == null) {
+ return AppOpsManager.opToDefaultMode(code);
+ }
+ return raw ? op.getMode() : op.uidState.evalMode(op.op, op.getMode());
+ }
}
@Override
@@ -487,8 +2365,7 @@ public class AppOpsService extends IAppOpsService.Stub {
@Override
public void setAudioRestriction(int code, int usage, int uid, int mode,
String[] exceptionPackages) {
- mAppOpsService.enforceManageAppOpsModes(Binder.getCallingPid(),
- Binder.getCallingUid(), uid);
+ enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
verifyIncomingUid(uid);
verifyIncomingOp(code);
@@ -496,35 +2373,58 @@ public class AppOpsService extends IAppOpsService.Stub {
code, usage, uid, mode, exceptionPackages);
mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsServiceInterface::notifyWatchersOfChange, mAppOpsService, code,
- UID_ANY));
+ AppOpsService::notifyWatchersOfChange, this, code, UID_ANY));
}
@Override
public void setCameraAudioRestriction(@CAMERA_AUDIO_RESTRICTION int mode) {
- mAppOpsService.enforceManageAppOpsModes(Binder.getCallingPid(),
- Binder.getCallingUid(), -1);
+ enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), -1);
mAudioRestrictionManager.setCameraAudioRestriction(mode);
mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsServiceInterface::notifyWatchersOfChange, mAppOpsService,
+ AppOpsService::notifyWatchersOfChange, this,
AppOpsManager.OP_PLAY_AUDIO, UID_ANY));
mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsServiceInterface::notifyWatchersOfChange, mAppOpsService,
+ AppOpsService::notifyWatchersOfChange, this,
AppOpsManager.OP_VIBRATE, UID_ANY));
}
@Override
public int checkPackage(int uid, String packageName) {
- return mAppOpsService.checkPackage(uid, packageName);
+ Objects.requireNonNull(packageName);
+ try {
+ verifyAndGetBypass(uid, packageName, null);
+ // When the caller is the system, it's possible that the packageName is the special
+ // one (e.g., "root") which isn't actually existed.
+ if (resolveUid(packageName) == uid
+ || (isPackageExisted(packageName)
+ && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(uid)))) {
+ return AppOpsManager.MODE_ALLOWED;
+ }
+ return AppOpsManager.MODE_ERRORED;
+ } catch (SecurityException ignored) {
+ return AppOpsManager.MODE_ERRORED;
+ }
}
private boolean isPackageExisted(String packageName) {
return getPackageManagerInternal().getPackageStateInternal(packageName) != null;
}
+ /**
+ * This method will check with PackageManager to determine if the package provided should
+ * be visible to the {@link Binder#getCallingUid()}.
+ *
+ * NOTE: This must not be called while synchronized on {@code this} to avoid dead locks
+ */
+ private boolean filterAppAccessUnlocked(String packageName, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ return LocalServices.getService(PackageManagerInternal.class)
+ .filterAppAccess(packageName, callingUid, userId);
+ }
+
@Override
public SyncNotedAppOp noteProxyOperation(int code, AttributionSource attributionSource,
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
@@ -570,20 +2470,13 @@ public class AppOpsService extends IAppOpsService.Stub {
final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY
: AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
- final int proxyReturn = mAppOpsService.noteOperationUnchecked(code, proxyUid,
+ final SyncNotedAppOp proxyReturn = noteOperationUnchecked(code, proxyUid,
resolveProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null,
- proxyFlags);
- if (proxyReturn != AppOpsManager.MODE_ALLOWED) {
- return new SyncNotedAppOp(proxyReturn, code, proxiedAttributionTag,
+ proxyFlags, !isProxyTrusted, "proxy " + message, shouldCollectMessage);
+ if (proxyReturn.getOpMode() != AppOpsManager.MODE_ALLOWED) {
+ return new SyncNotedAppOp(proxyReturn.getOpMode(), code, proxiedAttributionTag,
proxiedPackageName);
}
- if (shouldCollectAsyncNotedOp) {
- boolean isProxyAttributionTagValid = mAppOpsService.isAttributionTagValid(proxyUid,
- resolveProxyPackageName, proxyAttributionTag, null);
- collectAsyncNotedOp(proxyUid, resolveProxyPackageName, code,
- isProxyAttributionTagValid ? proxyAttributionTag : null, proxyFlags,
- message, shouldCollectMessage);
- }
}
String resolveProxiedPackageName = AppOpsManager.resolvePackageName(proxiedUid,
@@ -595,32 +2488,9 @@ public class AppOpsService extends IAppOpsService.Stub {
final int proxiedFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXIED
: AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED;
- final int result = mAppOpsService.noteOperationUnchecked(code, proxiedUid,
- resolveProxiedPackageName, proxiedAttributionTag, proxyUid, resolveProxyPackageName,
- proxyAttributionTag, proxiedFlags);
-
- boolean isProxiedAttributionTagValid = mAppOpsService.isAttributionTagValid(proxiedUid,
- resolveProxiedPackageName, proxiedAttributionTag, resolveProxyPackageName);
- if (shouldCollectAsyncNotedOp && result == AppOpsManager.MODE_ALLOWED) {
- collectAsyncNotedOp(proxiedUid, resolveProxiedPackageName, code,
- isProxiedAttributionTagValid ? proxiedAttributionTag : null, proxiedFlags,
- message, shouldCollectMessage);
- }
-
-
- return new SyncNotedAppOp(result, code,
- isProxiedAttributionTagValid ? proxiedAttributionTag : null,
- resolveProxiedPackageName);
- }
-
- private boolean isCallerAndAttributionTrusted(@NonNull AttributionSource attributionSource) {
- if (attributionSource.getUid() != Binder.getCallingUid()
- && attributionSource.isTrusted(mContext)) {
- return true;
- }
- return mContext.checkPermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
- Binder.getCallingPid(), Binder.getCallingUid(), null)
- == PackageManager.PERMISSION_GRANTED;
+ return noteOperationUnchecked(code, proxiedUid, resolveProxiedPackageName,
+ proxiedAttributionTag, proxyUid, resolveProxyPackageName, proxyAttributionTag,
+ proxiedFlags, shouldCollectAsyncNotedOp, message, shouldCollectMessage);
}
@Override
@@ -634,58 +2504,258 @@ public class AppOpsService extends IAppOpsService.Stub {
private SyncNotedAppOp noteOperationImpl(int code, int uid, @Nullable String packageName,
@Nullable String attributionTag, boolean shouldCollectAsyncNotedOp,
@Nullable String message, boolean shouldCollectMessage) {
+ verifyIncomingUid(uid);
+ verifyIncomingOp(code);
if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
packageName);
}
- int result = mAppOpsService.noteOperation(code, uid, packageName,
- attributionTag, message);
-
String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+ if (resolvedPackageName == null) {
+ return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
+ packageName);
+ }
+ return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
+ Process.INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF,
+ shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+ }
- boolean isAttributionTagValid = mAppOpsService.isAttributionTagValid(uid,
- resolvedPackageName, attributionTag, null);
-
- if (shouldCollectAsyncNotedOp && result == MODE_ALLOWED) {
- collectAsyncNotedOp(uid, resolvedPackageName, code,
- isAttributionTagValid ? attributionTag : null, AppOpsManager.OP_FLAG_SELF,
- message, shouldCollectMessage);
+ private SyncNotedAppOp noteOperationUnchecked(int code, int uid, @NonNull String packageName,
+ @Nullable String attributionTag, int proxyUid, String proxyPackageName,
+ @Nullable String proxyAttributionTag, @OpFlags int flags,
+ boolean shouldCollectAsyncNotedOp, @Nullable String message,
+ boolean shouldCollectMessage) {
+ PackageVerificationResult pvr;
+ try {
+ pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
+ boolean wasNull = attributionTag == null;
+ if (!pvr.isAttributionTagValid) {
+ attributionTag = null;
+ }
+ } catch (SecurityException e) {
+ Slog.e(TAG, "noteOperation", e);
+ return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
+ packageName);
}
- return new SyncNotedAppOp(result, code, isAttributionTagValid ? attributionTag : null,
- resolvedPackageName);
+ synchronized (this) {
+ final Ops ops = getOpsLocked(uid, packageName, attributionTag,
+ pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
+ if (ops == null) {
+ scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+ AppOpsManager.MODE_IGNORED);
+ if (DEBUG) Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
+ + " package " + packageName + "flags: " +
+ AppOpsManager.flagsToString(flags));
+ return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
+ packageName);
+ }
+ final Op op = getOpLocked(ops, code, uid, true);
+ final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
+ if (attributedOp.isRunning()) {
+ Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName + " code "
+ + code + " startTime of in progress event="
+ + attributedOp.mInProgressEvents.valueAt(0).getStartTime());
+ }
+
+ final int switchCode = AppOpsManager.opToSwitch(code);
+ final UidState uidState = ops.uidState;
+ if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, false)) {
+ attributedOp.rejected(uidState.getState(), flags);
+ scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+ AppOpsManager.MODE_IGNORED);
+ return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
+ packageName);
+ }
+ // If there is a non-default per UID policy (we set UID op mode only if
+ // non-default) it takes over, otherwise use the per package policy.
+ if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) {
+ final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode));
+ if (uidMode != AppOpsManager.MODE_ALLOWED) {
+ if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
+ + switchCode + " (" + code + ") uid " + uid + " package "
+ + packageName + " flags: " + AppOpsManager.flagsToString(flags));
+ attributedOp.rejected(uidState.getState(), flags);
+ scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+ uidMode);
+ return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
+ }
+ } else {
+ final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
+ : op;
+ final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode());
+ if (mode != AppOpsManager.MODE_ALLOWED) {
+ if (DEBUG) Slog.d(TAG, "noteOperation: reject #" + mode + " for code "
+ + switchCode + " (" + code + ") uid " + uid + " package "
+ + packageName + " flags: " + AppOpsManager.flagsToString(flags));
+ attributedOp.rejected(uidState.getState(), flags);
+ scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+ mode);
+ return new SyncNotedAppOp(mode, code, attributionTag, packageName);
+ }
+ }
+ if (DEBUG) {
+ Slog.d(TAG,
+ "noteOperation: allowing code " + code + " uid " + uid + " package "
+ + packageName + (attributionTag == null ? ""
+ : "." + attributionTag) + " flags: "
+ + AppOpsManager.flagsToString(flags));
+ }
+ scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+ AppOpsManager.MODE_ALLOWED);
+ attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag,
+ uidState.getState(),
+ flags);
+
+ if (shouldCollectAsyncNotedOp) {
+ collectAsyncNotedOp(uid, packageName, code, attributionTag, flags, message,
+ shouldCollectMessage);
+ }
+
+ return new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED, code, attributionTag,
+ packageName);
+ }
}
// TODO moltmann: Allow watching for attribution ops
@Override
public void startWatchingActive(int[] ops, IAppOpsActiveCallback callback) {
- mAppOpsService.startWatchingActive(ops, callback);
+ int watchedUid = Process.INVALID_UID;
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
+ != PackageManager.PERMISSION_GRANTED) {
+ watchedUid = callingUid;
+ }
+ if (ops != null) {
+ Preconditions.checkArrayElementsInRange(ops, 0,
+ AppOpsManager._NUM_OP - 1, "Invalid op code in: " + Arrays.toString(ops));
+ }
+ if (callback == null) {
+ return;
+ }
+ synchronized (this) {
+ SparseArray<ActiveCallback> callbacks = mActiveWatchers.get(callback.asBinder());
+ if (callbacks == null) {
+ callbacks = new SparseArray<>();
+ mActiveWatchers.put(callback.asBinder(), callbacks);
+ }
+ final ActiveCallback activeCallback = new ActiveCallback(callback, watchedUid,
+ callingUid, callingPid);
+ for (int op : ops) {
+ callbacks.put(op, activeCallback);
+ }
+ }
}
@Override
public void stopWatchingActive(IAppOpsActiveCallback callback) {
- mAppOpsService.stopWatchingActive(callback);
+ if (callback == null) {
+ return;
+ }
+ synchronized (this) {
+ final SparseArray<ActiveCallback> activeCallbacks =
+ mActiveWatchers.remove(callback.asBinder());
+ if (activeCallbacks == null) {
+ return;
+ }
+ final int callbackCount = activeCallbacks.size();
+ for (int i = 0; i < callbackCount; i++) {
+ activeCallbacks.valueAt(i).destroy();
+ }
+ }
}
@Override
public void startWatchingStarted(int[] ops, @NonNull IAppOpsStartedCallback callback) {
- mAppOpsService.startWatchingStarted(ops, callback);
+ int watchedUid = Process.INVALID_UID;
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
+ != PackageManager.PERMISSION_GRANTED) {
+ watchedUid = callingUid;
+ }
+
+ Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty");
+ Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1,
+ "Invalid op code in: " + Arrays.toString(ops));
+ Objects.requireNonNull(callback, "Callback cannot be null");
+
+ synchronized (this) {
+ SparseArray<StartedCallback> callbacks = mStartedWatchers.get(callback.asBinder());
+ if (callbacks == null) {
+ callbacks = new SparseArray<>();
+ mStartedWatchers.put(callback.asBinder(), callbacks);
+ }
+
+ final StartedCallback startedCallback = new StartedCallback(callback, watchedUid,
+ callingUid, callingPid);
+ for (int op : ops) {
+ callbacks.put(op, startedCallback);
+ }
+ }
}
@Override
public void stopWatchingStarted(IAppOpsStartedCallback callback) {
- mAppOpsService.stopWatchingStarted(callback);
+ Objects.requireNonNull(callback, "Callback cannot be null");
+
+ synchronized (this) {
+ final SparseArray<StartedCallback> startedCallbacks =
+ mStartedWatchers.remove(callback.asBinder());
+ if (startedCallbacks == null) {
+ return;
+ }
+
+ final int callbackCount = startedCallbacks.size();
+ for (int i = 0; i < callbackCount; i++) {
+ startedCallbacks.valueAt(i).destroy();
+ }
+ }
}
@Override
public void startWatchingNoted(@NonNull int[] ops, @NonNull IAppOpsNotedCallback callback) {
- mAppOpsService.startWatchingNoted(ops, callback);
+ int watchedUid = Process.INVALID_UID;
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
+ != PackageManager.PERMISSION_GRANTED) {
+ watchedUid = callingUid;
+ }
+ Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty");
+ Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1,
+ "Invalid op code in: " + Arrays.toString(ops));
+ Objects.requireNonNull(callback, "Callback cannot be null");
+ synchronized (this) {
+ SparseArray<NotedCallback> callbacks = mNotedWatchers.get(callback.asBinder());
+ if (callbacks == null) {
+ callbacks = new SparseArray<>();
+ mNotedWatchers.put(callback.asBinder(), callbacks);
+ }
+ final NotedCallback notedCallback = new NotedCallback(callback, watchedUid,
+ callingUid, callingPid);
+ for (int op : ops) {
+ callbacks.put(op, notedCallback);
+ }
+ }
}
@Override
public void stopWatchingNoted(IAppOpsNotedCallback callback) {
- mAppOpsService.stopWatchingNoted(callback);
+ Objects.requireNonNull(callback, "Callback cannot be null");
+ synchronized (this) {
+ final SparseArray<NotedCallback> notedCallbacks =
+ mNotedWatchers.remove(callback.asBinder());
+ if (notedCallbacks == null) {
+ return;
+ }
+ final int callbackCount = notedCallbacks.size();
+ for (int i = 0; i < callbackCount; i++) {
+ notedCallbacks.valueAt(i).destroy();
+ }
+ }
}
/**
@@ -772,7 +2842,7 @@ public class AppOpsService extends IAppOpsService.Stub {
int uid = Binder.getCallingUid();
Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
- mAppOpsService.verifyPackage(uid, packageName);
+ verifyAndGetBypass(uid, packageName, null);
synchronized (this) {
RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
@@ -802,7 +2872,7 @@ public class AppOpsService extends IAppOpsService.Stub {
int uid = Binder.getCallingUid();
Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
- mAppOpsService.verifyPackage(uid, packageName);
+ verifyAndGetBypass(uid, packageName, null);
synchronized (this) {
RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
@@ -821,7 +2891,7 @@ public class AppOpsService extends IAppOpsService.Stub {
int uid = Binder.getCallingUid();
- mAppOpsService.verifyPackage(uid, packageName);
+ verifyAndGetBypass(uid, packageName, null);
synchronized (this) {
return mUnforwardedAsyncNotedOps.remove(getAsyncNotedOpsKey(packageName, uid));
@@ -844,49 +2914,54 @@ public class AppOpsService extends IAppOpsService.Stub {
boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @NonNull String message,
boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
int attributionChainId) {
+ verifyIncomingUid(uid);
+ verifyIncomingOp(code);
if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
packageName);
}
- int result = mAppOpsService.startOperation(clientId, code, uid, packageName,
- attributionTag, startIfModeDefault, message,
- attributionFlags, attributionChainId);
-
String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
-
- boolean isAttributionTagValid = mAppOpsService.isAttributionTagValid(uid,
- resolvedPackageName, attributionTag, null);
-
- if (shouldCollectAsyncNotedOp && result == MODE_ALLOWED) {
- collectAsyncNotedOp(uid, resolvedPackageName, code,
- isAttributionTagValid ? attributionTag : null, AppOpsManager.OP_FLAG_SELF,
- message, shouldCollectMessage);
+ if (resolvedPackageName == null) {
+ return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
+ packageName);
}
- return new SyncNotedAppOp(result, code, isAttributionTagValid ? attributionTag : null,
- resolvedPackageName);
+ // As a special case for OP_RECORD_AUDIO_HOTWORD, which we use only for attribution
+ // purposes and not as a check, also make sure that the caller is allowed to access
+ // the data gated by OP_RECORD_AUDIO.
+ //
+ // TODO: Revert this change before Android 12.
+ if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO) {
+ int result = checkOperation(OP_RECORD_AUDIO, uid, packageName);
+ if (result != AppOpsManager.MODE_ALLOWED) {
+ return new SyncNotedAppOp(result, code, attributionTag, packageName);
+ }
+ }
+ return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
+ Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault,
+ shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags,
+ attributionChainId, /*dryRun*/ false);
}
@Override
- public SyncNotedAppOp startProxyOperation(IBinder clientId, int code,
+ public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean startIfModeDefault,
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
@AttributionFlags int proxiedAttributionFlags, int attributionChainId) {
- return mCheckOpsDelegateDispatcher.startProxyOperation(clientId, code,
- attributionSource, startIfModeDefault, shouldCollectAsyncNotedOp, message,
- shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
- proxiedAttributionFlags, attributionChainId);
+ return mCheckOpsDelegateDispatcher.startProxyOperation(clientId, code, attributionSource,
+ startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
+ skipProxyOperation, proxyAttributionFlags, proxiedAttributionFlags,
+ attributionChainId);
}
- private SyncNotedAppOp startProxyOperationImpl(IBinder clientId, int code,
+ private SyncNotedAppOp startProxyOperationImpl(@NonNull IBinder clientId, int code,
@NonNull AttributionSource attributionSource,
boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
boolean shouldCollectMessage, boolean skipProxyOperation, @AttributionFlags
int proxyAttributionFlags, @AttributionFlags int proxiedAttributionFlags,
int attributionChainId) {
-
final int proxyUid = attributionSource.getUid();
final String proxyPackageName = attributionSource.getPackageName();
final String proxyAttributionTag = attributionSource.getAttributionTag();
@@ -934,66 +3009,145 @@ public class AppOpsService extends IAppOpsService.Stub {
if (!skipProxyOperation) {
// Test if the proxied operation will succeed before starting the proxy operation
- final int testProxiedOp = mAppOpsService.startOperationUnchecked(clientId, code,
+ final SyncNotedAppOp testProxiedOp = startOperationUnchecked(clientId, code,
proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag, proxyUid,
resolvedProxyPackageName, proxyAttributionTag, proxiedFlags, startIfModeDefault,
+ shouldCollectAsyncNotedOp, message, shouldCollectMessage,
proxiedAttributionFlags, attributionChainId, /*dryRun*/ true);
-
- boolean isTestProxiedAttributionTagValid =
- mAppOpsService.isAttributionTagValid(proxiedUid, resolvedProxiedPackageName,
- proxiedAttributionTag, resolvedProxyPackageName);
-
- if (!shouldStartForMode(testProxiedOp, startIfModeDefault)) {
- return new SyncNotedAppOp(testProxiedOp, code,
- isTestProxiedAttributionTagValid ? proxiedAttributionTag : null,
- resolvedProxiedPackageName);
+ if (!shouldStartForMode(testProxiedOp.getOpMode(), startIfModeDefault)) {
+ return testProxiedOp;
}
final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY
: AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
- final int proxyAppOp = mAppOpsService.startOperationUnchecked(clientId, code, proxyUid,
+ final SyncNotedAppOp proxyAppOp = startOperationUnchecked(clientId, code, proxyUid,
resolvedProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null,
- proxyFlags, startIfModeDefault, proxyAttributionFlags, attributionChainId,
+ proxyFlags, startIfModeDefault, !isProxyTrusted, "proxy " + message,
+ shouldCollectMessage, proxyAttributionFlags, attributionChainId,
/*dryRun*/ false);
+ if (!shouldStartForMode(proxyAppOp.getOpMode(), startIfModeDefault)) {
+ return proxyAppOp;
+ }
+ }
- boolean isProxyAttributionTagValid = mAppOpsService.isAttributionTagValid(proxyUid,
- resolvedProxyPackageName, proxyAttributionTag, null);
+ return startOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
+ proxiedAttributionTag, proxyUid, resolvedProxyPackageName, proxyAttributionTag,
+ proxiedFlags, startIfModeDefault, shouldCollectAsyncNotedOp, message,
+ shouldCollectMessage, proxiedAttributionFlags, attributionChainId,
+ /*dryRun*/ false);
+ }
- if (!shouldStartForMode(proxyAppOp, startIfModeDefault)) {
- return new SyncNotedAppOp(proxyAppOp, code,
- isProxyAttributionTagValid ? proxyAttributionTag : null,
- resolvedProxyPackageName);
- }
+ private boolean shouldStartForMode(int mode, boolean startIfModeDefault) {
+ return (mode == MODE_ALLOWED || (mode == MODE_DEFAULT && startIfModeDefault));
+ }
- if (shouldCollectAsyncNotedOp) {
- collectAsyncNotedOp(proxyUid, resolvedProxyPackageName, code,
- isProxyAttributionTagValid ? proxyAttributionTag : null, proxyFlags,
- message, shouldCollectMessage);
+ private SyncNotedAppOp startOperationUnchecked(IBinder clientId, int code, int uid,
+ @NonNull String packageName, @Nullable String attributionTag, int proxyUid,
+ String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags,
+ boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @Nullable String message,
+ boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
+ int attributionChainId, boolean dryRun) {
+ PackageVerificationResult pvr;
+ try {
+ pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
+ if (!pvr.isAttributionTagValid) {
+ attributionTag = null;
}
+ } catch (SecurityException e) {
+ Slog.e(TAG, "startOperation", e);
+ return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
+ packageName);
}
- final int proxiedAppOp = mAppOpsService.startOperationUnchecked(clientId, code, proxiedUid,
- resolvedProxiedPackageName, proxiedAttributionTag, proxyUid,
- resolvedProxyPackageName, proxyAttributionTag, proxiedFlags, startIfModeDefault,
- proxiedAttributionFlags, attributionChainId,/*dryRun*/ false);
-
- boolean isProxiedAttributionTagValid = mAppOpsService.isAttributionTagValid(proxiedUid,
- resolvedProxiedPackageName, proxiedAttributionTag, resolvedProxyPackageName);
+ boolean isRestricted = false;
+ int startType = START_TYPE_FAILED;
+ synchronized (this) {
+ final Ops ops = getOpsLocked(uid, packageName, attributionTag,
+ pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
+ if (ops == null) {
+ if (!dryRun) {
+ scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
+ flags, AppOpsManager.MODE_IGNORED, startType, attributionFlags,
+ attributionChainId);
+ }
+ if (DEBUG) Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
+ + " package " + packageName + " flags: "
+ + AppOpsManager.flagsToString(flags));
+ return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
+ packageName);
+ }
+ final Op op = getOpLocked(ops, code, uid, true);
+ final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
+ final UidState uidState = ops.uidState;
+ isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass,
+ false);
+ final int switchCode = AppOpsManager.opToSwitch(code);
+ // If there is a non-default per UID policy (we set UID op mode only if
+ // non-default) it takes over, otherwise use the per package policy.
+ if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) {
+ final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode));
+ if (!shouldStartForMode(uidMode, startIfModeDefault)) {
+ if (DEBUG) {
+ Slog.d(TAG, "startOperation: uid reject #" + uidMode + " for code "
+ + switchCode + " (" + code + ") uid " + uid + " package "
+ + packageName + " flags: " + AppOpsManager.flagsToString(flags));
+ }
+ if (!dryRun) {
+ attributedOp.rejected(uidState.getState(), flags);
+ scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
+ flags, uidMode, startType, attributionFlags, attributionChainId);
+ }
+ return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
+ }
+ } else {
+ final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
+ : op;
+ final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode());
+ if (mode != AppOpsManager.MODE_ALLOWED
+ && (!startIfModeDefault || mode != MODE_DEFAULT)) {
+ if (DEBUG) Slog.d(TAG, "startOperation: reject #" + mode + " for code "
+ + switchCode + " (" + code + ") uid " + uid + " package "
+ + packageName + " flags: " + AppOpsManager.flagsToString(flags));
+ if (!dryRun) {
+ attributedOp.rejected(uidState.getState(), flags);
+ scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
+ flags, mode, startType, attributionFlags, attributionChainId);
+ }
+ return new SyncNotedAppOp(mode, code, attributionTag, packageName);
+ }
+ }
+ if (DEBUG) Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid
+ + " package " + packageName + " restricted: " + isRestricted
+ + " flags: " + AppOpsManager.flagsToString(flags));
+ if (!dryRun) {
+ try {
+ if (isRestricted) {
+ attributedOp.createPaused(clientId, proxyUid, proxyPackageName,
+ proxyAttributionTag, uidState.getState(), flags,
+ attributionFlags, attributionChainId);
+ } else {
+ attributedOp.started(clientId, proxyUid, proxyPackageName,
+ proxyAttributionTag, uidState.getState(), flags,
+ attributionFlags, attributionChainId);
+ startType = START_TYPE_STARTED;
+ }
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+ isRestricted ? MODE_IGNORED : MODE_ALLOWED, startType, attributionFlags,
+ attributionChainId);
+ }
+ }
- if (shouldCollectAsyncNotedOp && proxiedAppOp == MODE_ALLOWED) {
- collectAsyncNotedOp(proxyUid, resolvedProxiedPackageName, code,
- isProxiedAttributionTagValid ? proxiedAttributionTag : null,
- proxiedAttributionFlags, message, shouldCollectMessage);
+ if (shouldCollectAsyncNotedOp && !dryRun && !isRestricted) {
+ collectAsyncNotedOp(uid, packageName, code, attributionTag, AppOpsManager.OP_FLAG_SELF,
+ message, shouldCollectMessage);
}
- return new SyncNotedAppOp(proxiedAppOp, code,
- isProxiedAttributionTagValid ? proxiedAttributionTag : null,
- resolvedProxiedPackageName);
- }
-
- private boolean shouldStartForMode(int mode, boolean startIfModeDefault) {
- return (mode == MODE_ALLOWED || (mode == MODE_DEFAULT && startIfModeDefault));
+ return new SyncNotedAppOp(isRestricted ? MODE_IGNORED : MODE_ALLOWED, code, attributionTag,
+ packageName);
}
@Override
@@ -1005,11 +3159,22 @@ public class AppOpsService extends IAppOpsService.Stub {
private void finishOperationImpl(IBinder clientId, int code, int uid, String packageName,
String attributionTag) {
- mAppOpsService.finishOperation(clientId, code, uid, packageName, attributionTag);
+ verifyIncomingUid(uid);
+ verifyIncomingOp(code);
+ if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+ return;
+ }
+
+ String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+ if (resolvedPackageName == null) {
+ return;
+ }
+
+ finishOperationUnchecked(clientId, code, uid, resolvedPackageName, attributionTag);
}
@Override
- public void finishProxyOperation(IBinder clientId, int code,
+ public void finishProxyOperation(@NonNull IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
mCheckOpsDelegateDispatcher.finishProxyOperation(clientId, code, attributionSource,
skipProxyOperation);
@@ -1041,8 +3206,8 @@ public class AppOpsService extends IAppOpsService.Stub {
}
if (!skipProxyOperation) {
- mAppOpsService.finishOperationUnchecked(clientId, code, proxyUid,
- resolvedProxyPackageName, proxyAttributionTag);
+ finishOperationUnchecked(clientId, code, proxyUid, resolvedProxyPackageName,
+ proxyAttributionTag);
}
String resolvedProxiedPackageName = AppOpsManager.resolvePackageName(proxiedUid,
@@ -1051,12 +3216,209 @@ public class AppOpsService extends IAppOpsService.Stub {
return null;
}
- mAppOpsService.finishOperationUnchecked(clientId, code, proxiedUid,
- resolvedProxiedPackageName, proxiedAttributionTag);
+ finishOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
+ proxiedAttributionTag);
return null;
}
+ private void finishOperationUnchecked(IBinder clientId, int code, int uid, String packageName,
+ String attributionTag) {
+ PackageVerificationResult pvr;
+ try {
+ pvr = verifyAndGetBypass(uid, packageName, attributionTag);
+ if (!pvr.isAttributionTagValid) {
+ attributionTag = null;
+ }
+ } catch (SecurityException e) {
+ Slog.e(TAG, "Cannot finishOperation", e);
+ return;
+ }
+
+ synchronized (this) {
+ Op op = getOpLocked(code, uid, packageName, attributionTag, pvr.isAttributionTagValid,
+ pvr.bypass, /* edit */ true);
+ if (op == null) {
+ Slog.e(TAG, "Operation not found: uid=" + uid + " pkg=" + packageName + "("
+ + attributionTag + ") op=" + AppOpsManager.opToName(code));
+ return;
+ }
+ final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
+ if (attributedOp == null) {
+ Slog.e(TAG, "Attribution not found: uid=" + uid + " pkg=" + packageName + "("
+ + attributionTag + ") op=" + AppOpsManager.opToName(code));
+ return;
+ }
+
+ if (attributedOp.isRunning() || attributedOp.isPaused()) {
+ attributedOp.finished(clientId);
+ } else {
+ Slog.e(TAG, "Operation not started: uid=" + uid + " pkg=" + packageName + "("
+ + attributionTag + ") op=" + AppOpsManager.opToName(code));
+ }
+ }
+ }
+
+ void scheduleOpActiveChangedIfNeededLocked(int code, int uid, @NonNull
+ String packageName, @Nullable String attributionTag, boolean active, @AttributionFlags
+ int attributionFlags, int attributionChainId) {
+ ArraySet<ActiveCallback> dispatchedCallbacks = null;
+ final int callbackListCount = mActiveWatchers.size();
+ for (int i = 0; i < callbackListCount; i++) {
+ final SparseArray<ActiveCallback> callbacks = mActiveWatchers.valueAt(i);
+ ActiveCallback callback = callbacks.get(code);
+ if (callback != null) {
+ if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
+ continue;
+ }
+ if (dispatchedCallbacks == null) {
+ dispatchedCallbacks = new ArraySet<>();
+ }
+ dispatchedCallbacks.add(callback);
+ }
+ }
+ if (dispatchedCallbacks == null) {
+ return;
+ }
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsService::notifyOpActiveChanged,
+ this, dispatchedCallbacks, code, uid, packageName, attributionTag, active,
+ attributionFlags, attributionChainId));
+ }
+
+ private void notifyOpActiveChanged(ArraySet<ActiveCallback> callbacks,
+ int code, int uid, @NonNull String packageName, @Nullable String attributionTag,
+ boolean active, @AttributionFlags int attributionFlags, int attributionChainId) {
+ // There are features watching for mode changes such as window manager
+ // and location manager which are in our process. The callbacks in these
+ // features may require permissions our remote caller does not have.
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ final int callbackCount = callbacks.size();
+ for (int i = 0; i < callbackCount; i++) {
+ final ActiveCallback callback = callbacks.valueAt(i);
+ try {
+ if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
+ continue;
+ }
+ callback.mCallback.opActiveChanged(code, uid, packageName, attributionTag,
+ active, attributionFlags, attributionChainId);
+ } catch (RemoteException e) {
+ /* do nothing */
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ void scheduleOpStartedIfNeededLocked(int code, int uid, String pkgName,
+ String attributionTag, @OpFlags int flags, @Mode int result,
+ @AppOpsManager.OnOpStartedListener.StartedType int startedType,
+ @AttributionFlags int attributionFlags, int attributionChainId) {
+ ArraySet<StartedCallback> dispatchedCallbacks = null;
+ final int callbackListCount = mStartedWatchers.size();
+ for (int i = 0; i < callbackListCount; i++) {
+ final SparseArray<StartedCallback> callbacks = mStartedWatchers.valueAt(i);
+
+ StartedCallback callback = callbacks.get(code);
+ if (callback != null) {
+ if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
+ continue;
+ }
+
+ if (dispatchedCallbacks == null) {
+ dispatchedCallbacks = new ArraySet<>();
+ }
+ dispatchedCallbacks.add(callback);
+ }
+ }
+
+ if (dispatchedCallbacks == null) {
+ return;
+ }
+
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsService::notifyOpStarted,
+ this, dispatchedCallbacks, code, uid, pkgName, attributionTag, flags,
+ result, startedType, attributionFlags, attributionChainId));
+ }
+
+ private void notifyOpStarted(ArraySet<StartedCallback> callbacks,
+ int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
+ @Mode int result, @AppOpsManager.OnOpStartedListener.StartedType int startedType,
+ @AttributionFlags int attributionFlags, int attributionChainId) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ final int callbackCount = callbacks.size();
+ for (int i = 0; i < callbackCount; i++) {
+ final StartedCallback callback = callbacks.valueAt(i);
+ try {
+ if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
+ continue;
+ }
+ callback.mCallback.opStarted(code, uid, packageName, attributionTag, flags,
+ result, startedType, attributionFlags, attributionChainId);
+ } catch (RemoteException e) {
+ /* do nothing */
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ private void scheduleOpNotedIfNeededLocked(int code, int uid, String packageName,
+ String attributionTag, @OpFlags int flags, @Mode int result) {
+ ArraySet<NotedCallback> dispatchedCallbacks = null;
+ final int callbackListCount = mNotedWatchers.size();
+ for (int i = 0; i < callbackListCount; i++) {
+ final SparseArray<NotedCallback> callbacks = mNotedWatchers.valueAt(i);
+ final NotedCallback callback = callbacks.get(code);
+ if (callback != null) {
+ if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
+ continue;
+ }
+ if (dispatchedCallbacks == null) {
+ dispatchedCallbacks = new ArraySet<>();
+ }
+ dispatchedCallbacks.add(callback);
+ }
+ }
+ if (dispatchedCallbacks == null) {
+ return;
+ }
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsService::notifyOpChecked,
+ this, dispatchedCallbacks, code, uid, packageName, attributionTag, flags,
+ result));
+ }
+
+ private void notifyOpChecked(ArraySet<NotedCallback> callbacks,
+ int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
+ @Mode int result) {
+ // There are features watching for checks in our process. The callbacks in
+ // these features may require permissions our remote caller does not have.
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ final int callbackCount = callbacks.size();
+ for (int i = 0; i < callbackCount; i++) {
+ final NotedCallback callback = callbacks.valueAt(i);
+ try {
+ if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
+ continue;
+ }
+ callback.mCallback.opNoted(code, uid, packageName, attributionTag, flags,
+ result);
+ } catch (RemoteException e) {
+ /* do nothing */
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
@Override
public int permissionToOpCode(String permission) {
if (permission == null) {
@@ -1114,6 +3476,13 @@ public class AppOpsService extends IAppOpsService.Stub {
Binder.getCallingPid(), Binder.getCallingUid(), null);
}
+ private boolean shouldIgnoreCallback(int op, int watcherPid, int watcherUid) {
+ // If it's a restricted read op, ignore it if watcher doesn't have manage ops permission,
+ // as watcher should not use this to signal if the value is changed.
+ return opRestrictsRead(op) && mContext.checkPermission(Manifest.permission.MANAGE_APPOPS,
+ watcherPid, watcherUid) != PackageManager.PERMISSION_GRANTED;
+ }
+
private void verifyIncomingOp(int op) {
if (op >= 0 && op < AppOpsManager._NUM_OP) {
// Enforce manage appops permission if it's a restricted read op.
@@ -1154,6 +3523,35 @@ public class AppOpsService extends IAppOpsService.Stub {
|| resolveUid(resolvedPackage) != Process.INVALID_UID;
}
+ private boolean isCallerAndAttributionTrusted(@NonNull AttributionSource attributionSource) {
+ if (attributionSource.getUid() != Binder.getCallingUid()
+ && attributionSource.isTrusted(mContext)) {
+ return true;
+ }
+ return mContext.checkPermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid(), null)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ private @Nullable UidState getUidStateLocked(int uid, boolean edit) {
+ UidState uidState = mUidStates.get(uid);
+ if (uidState == null) {
+ if (!edit) {
+ return null;
+ }
+ uidState = new UidState(uid);
+ mUidStates.put(uid, uidState);
+ }
+
+ return uidState;
+ }
+
+ private void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, boolean visible) {
+ synchronized (this) {
+ getUidStateTracker().updateAppWidgetVisibility(uidPackageNames, visible);
+ }
+ }
+
/**
* @return {@link PackageManagerInternal}
*/
@@ -1165,6 +3563,801 @@ public class AppOpsService extends IAppOpsService.Stub {
return mPackageManagerInternal;
}
+ /**
+ * Create a restriction description matching the properties of the package.
+ *
+ * @param pkg The package to create the restriction description for
+ *
+ * @return The restriction matching the package
+ */
+ private RestrictionBypass getBypassforPackage(@NonNull AndroidPackage pkg) {
+ return new RestrictionBypass(pkg.getUid() == Process.SYSTEM_UID, pkg.isPrivileged(),
+ mContext.checkPermission(android.Manifest.permission
+ .EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS, -1, pkg.getUid())
+ == PackageManager.PERMISSION_GRANTED);
+ }
+
+ /**
+ * @see #verifyAndGetBypass(int, String, String, String)
+ */
+ private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
+ @Nullable String attributionTag) {
+ return verifyAndGetBypass(uid, packageName, attributionTag, null);
+ }
+
+ /**
+ * Verify that package belongs to uid and return the {@link RestrictionBypass bypass
+ * description} for the package, along with a boolean indicating whether the attribution tag is
+ * valid.
+ *
+ * @param uid The uid the package belongs to
+ * @param packageName The package the might belong to the uid
+ * @param attributionTag attribution tag or {@code null} if no need to verify
+ * @param proxyPackageName The proxy package, from which the attribution tag is to be pulled
+ *
+ * @return PackageVerificationResult containing {@link RestrictionBypass} and whether the
+ * attribution tag is valid
+ */
+ private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
+ @Nullable String attributionTag, @Nullable String proxyPackageName) {
+ if (uid == Process.ROOT_UID) {
+ // For backwards compatibility, don't check package name for root UID.
+ return new PackageVerificationResult(null,
+ /* isAttributionTagValid */ true);
+ }
+ if (Process.isSdkSandboxUid(uid)) {
+ // SDK sandbox processes run in their own UID range, but their associated
+ // UID for checks should always be the UID of the package implementing SDK sandbox
+ // service.
+ // TODO: We will need to modify the callers of this function instead, so
+ // modifications and checks against the app ops state are done with the
+ // correct UID.
+ try {
+ final PackageManager pm = mContext.getPackageManager();
+ final String supplementalPackageName = pm.getSdkSandboxPackageName();
+ if (Objects.equals(packageName, supplementalPackageName)) {
+ uid = pm.getPackageUidAsUser(supplementalPackageName,
+ PackageManager.PackageInfoFlags.of(0), UserHandle.getUserId(uid));
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // Shouldn't happen for the supplemental package
+ e.printStackTrace();
+ }
+ }
+
+
+ // Do not check if uid/packageName/attributionTag is already known.
+ synchronized (this) {
+ UidState uidState = mUidStates.get(uid);
+ if (uidState != null && uidState.pkgOps != null) {
+ Ops ops = uidState.pkgOps.get(packageName);
+
+ if (ops != null && (attributionTag == null || ops.knownAttributionTags.contains(
+ attributionTag)) && ops.bypass != null) {
+ return new PackageVerificationResult(ops.bypass,
+ ops.validAttributionTags.contains(attributionTag));
+ }
+ }
+ }
+
+ int callingUid = Binder.getCallingUid();
+
+ // Allow any attribution tag for resolvable uids
+ int pkgUid;
+ if (Objects.equals(packageName, "com.android.shell")) {
+ // Special case for the shell which is a package but should be able
+ // to bypass app attribution tag restrictions.
+ pkgUid = Process.SHELL_UID;
+ } else {
+ pkgUid = resolveUid(packageName);
+ }
+ if (pkgUid != Process.INVALID_UID) {
+ if (pkgUid != UserHandle.getAppId(uid)) {
+ Slog.e(TAG, "Bad call made by uid " + callingUid + ". "
+ + "Package \"" + packageName + "\" does not belong to uid " + uid + ".");
+ String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not";
+ throw new SecurityException("Specified package \"" + packageName + "\" under uid "
+ + UserHandle.getAppId(uid) + otherUidMessage);
+ }
+ return new PackageVerificationResult(RestrictionBypass.UNRESTRICTED,
+ /* isAttributionTagValid */ true);
+ }
+
+ int userId = UserHandle.getUserId(uid);
+ RestrictionBypass bypass = null;
+ boolean isAttributionTagValid = false;
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
+ AndroidPackage pkg = pmInt.getPackage(packageName);
+ if (pkg != null) {
+ isAttributionTagValid = isAttributionInPackage(pkg, attributionTag);
+ pkgUid = UserHandle.getUid(userId, UserHandle.getAppId(pkg.getUid()));
+ bypass = getBypassforPackage(pkg);
+ }
+ if (!isAttributionTagValid) {
+ AndroidPackage proxyPkg = proxyPackageName != null
+ ? pmInt.getPackage(proxyPackageName) : null;
+ // Re-check in proxy.
+ isAttributionTagValid = isAttributionInPackage(proxyPkg, attributionTag);
+ String msg;
+ if (pkg != null && isAttributionTagValid) {
+ msg = "attributionTag " + attributionTag + " declared in manifest of the proxy"
+ + " package " + proxyPackageName + ", this is not advised";
+ } else if (pkg != null) {
+ msg = "attributionTag " + attributionTag + " not declared in manifest of "
+ + packageName;
+ } else {
+ msg = "package " + packageName + " not found, can't check for "
+ + "attributionTag " + attributionTag;
+ }
+
+ try {
+ if (!mPlatformCompat.isChangeEnabledByPackageName(
+ SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE, packageName,
+ userId) || !mPlatformCompat.isChangeEnabledByUid(
+ SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE,
+ callingUid)) {
+ // Do not override tags if overriding is not enabled for this package
+ isAttributionTagValid = true;
+ }
+ Slog.e(TAG, msg);
+ } catch (RemoteException neverHappens) {
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+
+ if (pkgUid != uid) {
+ Slog.e(TAG, "Bad call made by uid " + callingUid + ". "
+ + "Package \"" + packageName + "\" does not belong to uid " + uid + ".");
+ String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not";
+ throw new SecurityException("Specified package \"" + packageName + "\" under uid " + uid
+ + otherUidMessage);
+ }
+
+ return new PackageVerificationResult(bypass, isAttributionTagValid);
+ }
+
+ private boolean isAttributionInPackage(@Nullable AndroidPackage pkg,
+ @Nullable String attributionTag) {
+ if (pkg == null) {
+ return false;
+ } else if (attributionTag == null) {
+ return true;
+ }
+ if (pkg.getAttributions() != null) {
+ int numAttributions = pkg.getAttributions().size();
+ for (int i = 0; i < numAttributions; i++) {
+ if (pkg.getAttributions().get(i).getTag().equals(attributionTag)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get (and potentially create) ops.
+ *
+ * @param uid The uid the package belongs to
+ * @param packageName The name of the package
+ * @param attributionTag attribution tag
+ * @param isAttributionTagValid whether the given attribution tag is valid
+ * @param bypass When to bypass certain op restrictions (can be null if edit == false)
+ * @param edit If an ops does not exist, create the ops?
+
+ * @return The ops
+ */
+ private Ops getOpsLocked(int uid, String packageName, @Nullable String attributionTag,
+ boolean isAttributionTagValid, @Nullable RestrictionBypass bypass, boolean edit) {
+ UidState uidState = getUidStateLocked(uid, edit);
+ if (uidState == null) {
+ return null;
+ }
+
+ if (uidState.pkgOps == null) {
+ if (!edit) {
+ return null;
+ }
+ uidState.pkgOps = new ArrayMap<>();
+ }
+
+ Ops ops = uidState.pkgOps.get(packageName);
+ if (ops == null) {
+ if (!edit) {
+ return null;
+ }
+ ops = new Ops(packageName, uidState);
+ uidState.pkgOps.put(packageName, ops);
+ }
+
+ if (edit) {
+ if (bypass != null) {
+ ops.bypass = bypass;
+ }
+
+ if (attributionTag != null) {
+ ops.knownAttributionTags.add(attributionTag);
+ if (isAttributionTagValid) {
+ ops.validAttributionTags.add(attributionTag);
+ } else {
+ ops.validAttributionTags.remove(attributionTag);
+ }
+ }
+ }
+
+ return ops;
+ }
+
+ @Override
+ public void scheduleWriteLocked() {
+ if (!mWriteScheduled) {
+ mWriteScheduled = true;
+ mHandler.postDelayed(mWriteRunner, WRITE_DELAY);
+ }
+ }
+
+ @Override
+ public void scheduleFastWriteLocked() {
+ if (!mFastWriteScheduled) {
+ mWriteScheduled = true;
+ mFastWriteScheduled = true;
+ mHandler.removeCallbacks(mWriteRunner);
+ mHandler.postDelayed(mWriteRunner, 10*1000);
+ }
+ }
+
+ /**
+ * Get the state of an op for a uid.
+ *
+ * @param code The code of the op
+ * @param uid The uid the of the package
+ * @param packageName The package name for which to get the state for
+ * @param attributionTag The attribution tag
+ * @param isAttributionTagValid Whether the given attribution tag is valid
+ * @param bypass When to bypass certain op restrictions (can be null if edit == false)
+ * @param edit Iff {@code true} create the {@link Op} object if not yet created
+ *
+ * @return The {@link Op state} of the op
+ */
+ private @Nullable Op getOpLocked(int code, int uid, @NonNull String packageName,
+ @Nullable String attributionTag, boolean isAttributionTagValid,
+ @Nullable RestrictionBypass bypass, boolean edit) {
+ Ops ops = getOpsLocked(uid, packageName, attributionTag, isAttributionTagValid, bypass,
+ edit);
+ if (ops == null) {
+ return null;
+ }
+ return getOpLocked(ops, code, uid, edit);
+ }
+
+ private Op getOpLocked(Ops ops, int code, int uid, boolean edit) {
+ Op op = ops.get(code);
+ if (op == null) {
+ if (!edit) {
+ return null;
+ }
+ op = new Op(ops.uidState, ops.packageName, code, uid);
+ ops.put(code, op);
+ }
+ if (edit) {
+ scheduleWriteLocked();
+ }
+ return op;
+ }
+
+ private boolean isOpRestrictedDueToSuspend(int code, String packageName, int uid) {
+ if (!ArrayUtils.contains(OPS_RESTRICTED_ON_SUSPEND, code)) {
+ return false;
+ }
+ final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
+ return pmi.isPackageSuspended(packageName, UserHandle.getUserId(uid));
+ }
+
+ private boolean isOpRestrictedLocked(int uid, int code, String packageName,
+ String attributionTag, @Nullable RestrictionBypass appBypass, boolean isCheckOp) {
+ int restrictionSetCount = mOpGlobalRestrictions.size();
+
+ for (int i = 0; i < restrictionSetCount; i++) {
+ ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.valueAt(i);
+ if (restrictionState.hasRestriction(code)) {
+ return true;
+ }
+ }
+
+ int userHandle = UserHandle.getUserId(uid);
+ restrictionSetCount = mOpUserRestrictions.size();
+
+ for (int i = 0; i < restrictionSetCount; i++) {
+ // For each client, check that the given op is not restricted, or that the given
+ // package is exempt from the restriction.
+ ClientUserRestrictionState restrictionState = mOpUserRestrictions.valueAt(i);
+ if (restrictionState.hasRestriction(code, packageName, attributionTag, userHandle,
+ isCheckOp)) {
+ RestrictionBypass opBypass = opAllowSystemBypassRestriction(code);
+ if (opBypass != null) {
+ // If we are the system, bypass user restrictions for certain codes
+ synchronized (this) {
+ if (opBypass.isSystemUid && appBypass != null && appBypass.isSystemUid) {
+ return false;
+ }
+ if (opBypass.isPrivileged && appBypass != null && appBypass.isPrivileged) {
+ return false;
+ }
+ if (opBypass.isRecordAudioRestrictionExcept && appBypass != null
+ && appBypass.isRecordAudioRestrictionExcept) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void readState() {
+ synchronized (mFile) {
+ synchronized (this) {
+ FileInputStream stream;
+ try {
+ stream = mFile.openRead();
+ } catch (FileNotFoundException e) {
+ Slog.i(TAG, "No existing app ops " + mFile.getBaseFile() + "; starting empty");
+ return;
+ }
+ boolean success = false;
+ mUidStates.clear();
+ mAppOpsCheckingService.clearAllModes();
+ try {
+ TypedXmlPullParser parser = Xml.resolvePullParser(stream);
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ ;
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ throw new IllegalStateException("no start tag found");
+ }
+
+ mVersionAtBoot = parser.getAttributeInt(null, "v", NO_VERSION);
+
+ int outerDepth = parser.getDepth();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals("pkg")) {
+ readPackage(parser);
+ } else if (tagName.equals("uid")) {
+ readUidOps(parser);
+ } else {
+ Slog.w(TAG, "Unknown element under <app-ops>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ success = true;
+ } catch (IllegalStateException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } catch (NullPointerException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } catch (NumberFormatException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } catch (XmlPullParserException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } catch (IndexOutOfBoundsException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } finally {
+ if (!success) {
+ mUidStates.clear();
+ mAppOpsCheckingService.clearAllModes();
+ }
+ try {
+ stream.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ @GuardedBy("this")
+ void upgradeRunAnyInBackgroundLocked() {
+ for (int i = 0; i < mUidStates.size(); i++) {
+ final UidState uidState = mUidStates.valueAt(i);
+ if (uidState == null) {
+ continue;
+ }
+ SparseIntArray opModes = uidState.getNonDefaultUidModes();
+ if (opModes != null) {
+ final int idx = opModes.indexOfKey(AppOpsManager.OP_RUN_IN_BACKGROUND);
+ if (idx >= 0) {
+ uidState.setUidMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND,
+ opModes.valueAt(idx));
+ }
+ }
+ if (uidState.pkgOps == null) {
+ continue;
+ }
+ boolean changed = false;
+ for (int j = 0; j < uidState.pkgOps.size(); j++) {
+ Ops ops = uidState.pkgOps.valueAt(j);
+ if (ops != null) {
+ final Op op = ops.get(AppOpsManager.OP_RUN_IN_BACKGROUND);
+ if (op != null && op.getMode() != AppOpsManager.opToDefaultMode(op.op)) {
+ final Op copy = new Op(op.uidState, op.packageName,
+ AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uidState.uid);
+ copy.setMode(op.getMode());
+ ops.put(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, copy);
+ changed = true;
+ }
+ }
+ }
+ if (changed) {
+ uidState.evalForegroundOps();
+ }
+ }
+ }
+
+ /**
+ * The interpretation of the default mode - MODE_DEFAULT - for OP_SCHEDULE_EXACT_ALARM is
+ * changing. Simultaneously, we want to change this op's mode from MODE_DEFAULT to MODE_ALLOWED
+ * for already installed apps. For newer apps, it will stay as MODE_DEFAULT.
+ */
+ @VisibleForTesting
+ @GuardedBy("this")
+ void upgradeScheduleExactAlarmLocked() {
+ final PermissionManagerServiceInternal pmsi = LocalServices.getService(
+ PermissionManagerServiceInternal.class);
+ final UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
+ final PackageManagerInternal pmi = getPackageManagerInternal();
+
+ final String[] packagesDeclaringPermission = pmsi.getAppOpPermissionPackages(
+ AppOpsManager.opToPermission(OP_SCHEDULE_EXACT_ALARM));
+ final int[] userIds = umi.getUserIds();
+
+ for (final String pkg : packagesDeclaringPermission) {
+ for (int userId : userIds) {
+ final int uid = pmi.getPackageUid(pkg, 0, userId);
+
+ UidState uidState = mUidStates.get(uid);
+ if (uidState == null) {
+ uidState = new UidState(uid);
+ mUidStates.put(uid, uidState);
+ }
+ final int oldMode = uidState.getUidMode(OP_SCHEDULE_EXACT_ALARM);
+ if (oldMode == AppOpsManager.opToDefaultMode(OP_SCHEDULE_EXACT_ALARM)) {
+ uidState.setUidMode(OP_SCHEDULE_EXACT_ALARM, MODE_ALLOWED);
+ }
+ }
+ // This appop is meant to be controlled at a uid level. So we leave package modes as
+ // they are.
+ }
+ }
+
+ private void upgradeLocked(int oldVersion) {
+ if (oldVersion >= CURRENT_VERSION) {
+ return;
+ }
+ Slog.d(TAG, "Upgrading app-ops xml from version " + oldVersion + " to " + CURRENT_VERSION);
+ switch (oldVersion) {
+ case NO_VERSION:
+ upgradeRunAnyInBackgroundLocked();
+ // fall through
+ case 1:
+ upgradeScheduleExactAlarmLocked();
+ // fall through
+ case 2:
+ // for future upgrades
+ }
+ scheduleFastWriteLocked();
+ }
+
+ private void readUidOps(TypedXmlPullParser parser) throws NumberFormatException,
+ XmlPullParserException, IOException {
+ final int uid = parser.getAttributeInt(null, "n");
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals("op")) {
+ final int code = parser.getAttributeInt(null, "n");
+ final int mode = parser.getAttributeInt(null, "m");
+ setUidMode(code, uid, mode);
+ } else {
+ Slog.w(TAG, "Unknown element under <uid-ops>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+
+ private void readPackage(TypedXmlPullParser parser)
+ throws NumberFormatException, XmlPullParserException, IOException {
+ String pkgName = parser.getAttributeValue(null, "n");
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals("uid")) {
+ readUid(parser, pkgName);
+ } else {
+ Slog.w(TAG, "Unknown element under <pkg>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+
+ private void readUid(TypedXmlPullParser parser, String pkgName)
+ throws NumberFormatException, XmlPullParserException, IOException {
+ int uid = parser.getAttributeInt(null, "n");
+ final UidState uidState = getUidStateLocked(uid, true);
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ String tagName = parser.getName();
+ if (tagName.equals("op")) {
+ readOp(parser, uidState, pkgName);
+ } else {
+ Slog.w(TAG, "Unknown element under <pkg>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ uidState.evalForegroundOps();
+ }
+
+ private void readAttributionOp(TypedXmlPullParser parser, @NonNull Op parent,
+ @Nullable String attribution)
+ throws NumberFormatException, IOException, XmlPullParserException {
+ final AttributedOp attributedOp = parent.getOrCreateAttribution(parent, attribution);
+
+ final long key = parser.getAttributeLong(null, "n");
+ final int uidState = extractUidStateFromKey(key);
+ final int opFlags = extractFlagsFromKey(key);
+
+ final long accessTime = parser.getAttributeLong(null, "t", 0);
+ final long rejectTime = parser.getAttributeLong(null, "r", 0);
+ final long accessDuration = parser.getAttributeLong(null, "d", -1);
+ final String proxyPkg = XmlUtils.readStringAttribute(parser, "pp");
+ final int proxyUid = parser.getAttributeInt(null, "pu", Process.INVALID_UID);
+ final String proxyAttributionTag = XmlUtils.readStringAttribute(parser, "pc");
+
+ if (accessTime > 0) {
+ attributedOp.accessed(accessTime, accessDuration, proxyUid, proxyPkg,
+ proxyAttributionTag, uidState, opFlags);
+ }
+ if (rejectTime > 0) {
+ attributedOp.rejected(rejectTime, uidState, opFlags);
+ }
+ }
+
+ private void readOp(TypedXmlPullParser parser,
+ @NonNull UidState uidState, @NonNull String pkgName)
+ throws NumberFormatException, XmlPullParserException, IOException {
+ int opCode = parser.getAttributeInt(null, "n");
+ Op op = new Op(uidState, pkgName, opCode, uidState.uid);
+
+ final int mode = parser.getAttributeInt(null, "m", AppOpsManager.opToDefaultMode(op.op));
+ op.setMode(mode);
+
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ String tagName = parser.getName();
+ if (tagName.equals("st")) {
+ readAttributionOp(parser, op, XmlUtils.readStringAttribute(parser, "id"));
+ } else {
+ Slog.w(TAG, "Unknown element under <op>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+
+ if (uidState.pkgOps == null) {
+ uidState.pkgOps = new ArrayMap<>();
+ }
+ Ops ops = uidState.pkgOps.get(pkgName);
+ if (ops == null) {
+ ops = new Ops(pkgName, uidState);
+ uidState.pkgOps.put(pkgName, ops);
+ }
+ ops.put(op.op, op);
+ }
+
+ void writeState() {
+ synchronized (mFile) {
+ FileOutputStream stream;
+ try {
+ stream = mFile.startWrite();
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to write state: " + e);
+ return;
+ }
+
+ List<AppOpsManager.PackageOps> allOps = getPackagesForOps(null);
+
+ try {
+ TypedXmlSerializer out = Xml.resolveSerializer(stream);
+ out.startDocument(null, true);
+ out.startTag(null, "app-ops");
+ out.attributeInt(null, "v", CURRENT_VERSION);
+
+ SparseArray<SparseIntArray> uidStatesClone;
+ synchronized (this) {
+ uidStatesClone = new SparseArray<>(mUidStates.size());
+
+ final int uidStateCount = mUidStates.size();
+ for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) {
+ UidState uidState = mUidStates.valueAt(uidStateNum);
+ int uid = mUidStates.keyAt(uidStateNum);
+
+ SparseIntArray opModes = uidState.getNonDefaultUidModes();
+ if (opModes != null && opModes.size() > 0) {
+ uidStatesClone.put(uid, opModes);
+ }
+ }
+ }
+
+ final int uidStateCount = uidStatesClone.size();
+ for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) {
+ SparseIntArray opModes = uidStatesClone.valueAt(uidStateNum);
+ if (opModes != null && opModes.size() > 0) {
+ out.startTag(null, "uid");
+ out.attributeInt(null, "n", uidStatesClone.keyAt(uidStateNum));
+ final int opCount = opModes.size();
+ for (int opCountNum = 0; opCountNum < opCount; opCountNum++) {
+ final int op = opModes.keyAt(opCountNum);
+ final int mode = opModes.valueAt(opCountNum);
+ out.startTag(null, "op");
+ out.attributeInt(null, "n", op);
+ out.attributeInt(null, "m", mode);
+ out.endTag(null, "op");
+ }
+ out.endTag(null, "uid");
+ }
+ }
+
+ if (allOps != null) {
+ String lastPkg = null;
+ for (int i=0; i<allOps.size(); i++) {
+ AppOpsManager.PackageOps pkg = allOps.get(i);
+ if (!Objects.equals(pkg.getPackageName(), lastPkg)) {
+ if (lastPkg != null) {
+ out.endTag(null, "pkg");
+ }
+ lastPkg = pkg.getPackageName();
+ if (lastPkg != null) {
+ out.startTag(null, "pkg");
+ out.attribute(null, "n", lastPkg);
+ }
+ }
+ out.startTag(null, "uid");
+ out.attributeInt(null, "n", pkg.getUid());
+ List<AppOpsManager.OpEntry> ops = pkg.getOps();
+ for (int j=0; j<ops.size(); j++) {
+ AppOpsManager.OpEntry op = ops.get(j);
+ out.startTag(null, "op");
+ out.attributeInt(null, "n", op.getOp());
+ if (op.getMode() != AppOpsManager.opToDefaultMode(op.getOp())) {
+ out.attributeInt(null, "m", op.getMode());
+ }
+
+ for (String attributionTag : op.getAttributedOpEntries().keySet()) {
+ final AttributedOpEntry attribution =
+ op.getAttributedOpEntries().get(attributionTag);
+
+ final ArraySet<Long> keys = attribution.collectKeys();
+
+ final int keyCount = keys.size();
+ for (int k = 0; k < keyCount; k++) {
+ final long key = keys.valueAt(k);
+
+ final int uidState = AppOpsManager.extractUidStateFromKey(key);
+ final int flags = AppOpsManager.extractFlagsFromKey(key);
+
+ final long accessTime = attribution.getLastAccessTime(uidState,
+ uidState, flags);
+ final long rejectTime = attribution.getLastRejectTime(uidState,
+ uidState, flags);
+ final long accessDuration = attribution.getLastDuration(
+ uidState, uidState, flags);
+ // Proxy information for rejections is not backed up
+ final OpEventProxyInfo proxy = attribution.getLastProxyInfo(
+ uidState, uidState, flags);
+
+ if (accessTime <= 0 && rejectTime <= 0 && accessDuration <= 0
+ && proxy == null) {
+ continue;
+ }
+
+ String proxyPkg = null;
+ String proxyAttributionTag = null;
+ int proxyUid = Process.INVALID_UID;
+ if (proxy != null) {
+ proxyPkg = proxy.getPackageName();
+ proxyAttributionTag = proxy.getAttributionTag();
+ proxyUid = proxy.getUid();
+ }
+
+ out.startTag(null, "st");
+ if (attributionTag != null) {
+ out.attribute(null, "id", attributionTag);
+ }
+ out.attributeLong(null, "n", key);
+ if (accessTime > 0) {
+ out.attributeLong(null, "t", accessTime);
+ }
+ if (rejectTime > 0) {
+ out.attributeLong(null, "r", rejectTime);
+ }
+ if (accessDuration > 0) {
+ out.attributeLong(null, "d", accessDuration);
+ }
+ if (proxyPkg != null) {
+ out.attribute(null, "pp", proxyPkg);
+ }
+ if (proxyAttributionTag != null) {
+ out.attribute(null, "pc", proxyAttributionTag);
+ }
+ if (proxyUid >= 0) {
+ out.attributeInt(null, "pu", proxyUid);
+ }
+ out.endTag(null, "st");
+ }
+ }
+
+ out.endTag(null, "op");
+ }
+ out.endTag(null, "uid");
+ }
+ if (lastPkg != null) {
+ out.endTag(null, "pkg");
+ }
+ }
+
+ out.endTag(null, "app-ops");
+ out.endDocument();
+ mFile.finishWrite(stream);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to write state, restoring backup.", e);
+ mFile.failWrite(stream);
+ }
+ }
+ mHistoricalRegistry.writeAndClearDiscreteHistory();
+ }
+
static class Shell extends ShellCommand {
final IAppOpsService mInterface;
final AppOpsService mInternal;
@@ -1178,6 +4371,7 @@ public class AppOpsService extends IAppOpsService.Stub {
int mode;
int packageUid;
int nonpackageUid;
+ final static Binder sBinder = new Binder();
IBinder mToken;
boolean targetsUid;
@@ -1198,7 +4392,7 @@ public class AppOpsService extends IAppOpsService.Stub {
dumpCommandHelp(pw);
}
- static int strOpToOp(String op, PrintWriter err) {
+ static private int strOpToOp(String op, PrintWriter err) {
try {
return AppOpsManager.strOpToOp(op);
} catch (IllegalArgumentException e) {
@@ -1395,24 +4589,6 @@ public class AppOpsService extends IAppOpsService.Stub {
pw.println(" not specified, the current user is assumed.");
}
- @Override
- protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- mAppOpsService.dump(fd, pw, args);
-
- pw.println();
- if (mCheckOpsDelegateDispatcher.mPolicy != null
- && mCheckOpsDelegateDispatcher.mPolicy instanceof AppOpsPolicy) {
- AppOpsPolicy policy = (AppOpsPolicy) mCheckOpsDelegateDispatcher.mPolicy;
- policy.dumpTags(pw);
- } else {
- pw.println(" AppOps policy not set.");
- }
-
- if (mAudioRestrictionManager.hasActiveRestrictions()) {
- pw.println();
- mAudioRestrictionManager.dump(pw);
- }
- }
static int onShellCommand(Shell shell, String cmd) {
if (cmd == null) {
return shell.handleDefaultCommands(cmd);
@@ -1616,12 +4792,14 @@ public class AppOpsService extends IAppOpsService.Stub {
return 0;
}
case "write-settings": {
- shell.mInternal.mAppOpsService
- .enforceManageAppOpsModes(Binder.getCallingPid(),
+ shell.mInternal.enforceManageAppOpsModes(Binder.getCallingPid(),
Binder.getCallingUid(), -1);
final long token = Binder.clearCallingIdentity();
try {
- shell.mInternal.mAppOpsService.writeState();
+ synchronized (shell.mInternal) {
+ shell.mInternal.mHandler.removeCallbacks(shell.mInternal.mWriteRunner);
+ }
+ shell.mInternal.writeState();
pw.println("Current settings written.");
} finally {
Binder.restoreCallingIdentity(token);
@@ -1629,12 +4807,11 @@ public class AppOpsService extends IAppOpsService.Stub {
return 0;
}
case "read-settings": {
- shell.mInternal.mAppOpsService
- .enforceManageAppOpsModes(Binder.getCallingPid(),
- Binder.getCallingUid(), -1);
+ shell.mInternal.enforceManageAppOpsModes(Binder.getCallingPid(),
+ Binder.getCallingUid(), -1);
final long token = Binder.clearCallingIdentity();
try {
- shell.mInternal.mAppOpsService.readState();
+ shell.mInternal.readState();
pw.println("Last settings read.");
} finally {
Binder.restoreCallingIdentity(token);
@@ -1680,70 +4857,877 @@ public class AppOpsService extends IAppOpsService.Stub {
return -1;
}
+ private void dumpHelp(PrintWriter pw) {
+ pw.println("AppOps service (appops) dump options:");
+ pw.println(" -h");
+ pw.println(" Print this help text.");
+ pw.println(" --op [OP]");
+ pw.println(" Limit output to data associated with the given app op code.");
+ pw.println(" --mode [MODE]");
+ pw.println(" Limit output to data associated with the given app op mode.");
+ pw.println(" --package [PACKAGE]");
+ pw.println(" Limit output to data associated with the given package name.");
+ pw.println(" --attributionTag [attributionTag]");
+ pw.println(" Limit output to data associated with the given attribution tag.");
+ pw.println(" --include-discrete [n]");
+ pw.println(" Include discrete ops limited to n per dimension. Use zero for no limit.");
+ pw.println(" --watchers");
+ pw.println(" Only output the watcher sections.");
+ pw.println(" --history");
+ pw.println(" Only output history.");
+ pw.println(" --uid-state-changes");
+ pw.println(" Include logs about uid state changes.");
+ }
+
+ private void dumpStatesLocked(@NonNull PrintWriter pw, @Nullable String filterAttributionTag,
+ @HistoricalOpsRequestFilter int filter, long nowElapsed, @NonNull Op op, long now,
+ @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix) {
+ final int numAttributions = op.mAttributions.size();
+ for (int i = 0; i < numAttributions; i++) {
+ if ((filter & FILTER_BY_ATTRIBUTION_TAG) != 0 && !Objects.equals(
+ op.mAttributions.keyAt(i), filterAttributionTag)) {
+ continue;
+ }
+
+ pw.print(prefix + op.mAttributions.keyAt(i) + "=[\n");
+ dumpStatesLocked(pw, nowElapsed, op, op.mAttributions.keyAt(i), now, sdf, date,
+ prefix + " ");
+ pw.print(prefix + "]\n");
+ }
+ }
+
+ private void dumpStatesLocked(@NonNull PrintWriter pw, long nowElapsed, @NonNull Op op,
+ @Nullable String attributionTag, long now, @NonNull SimpleDateFormat sdf,
+ @NonNull Date date, @NonNull String prefix) {
+
+ final AttributedOpEntry entry = op.createSingleAttributionEntryLocked(
+ attributionTag).getAttributedOpEntries().get(attributionTag);
+
+ final ArraySet<Long> keys = entry.collectKeys();
+
+ final int keyCount = keys.size();
+ for (int k = 0; k < keyCount; k++) {
+ final long key = keys.valueAt(k);
+
+ final int uidState = AppOpsManager.extractUidStateFromKey(key);
+ final int flags = AppOpsManager.extractFlagsFromKey(key);
+
+ final long accessTime = entry.getLastAccessTime(uidState, uidState, flags);
+ final long rejectTime = entry.getLastRejectTime(uidState, uidState, flags);
+ final long accessDuration = entry.getLastDuration(uidState, uidState, flags);
+ final OpEventProxyInfo proxy = entry.getLastProxyInfo(uidState, uidState, flags);
+
+ String proxyPkg = null;
+ String proxyAttributionTag = null;
+ int proxyUid = Process.INVALID_UID;
+ if (proxy != null) {
+ proxyPkg = proxy.getPackageName();
+ proxyAttributionTag = proxy.getAttributionTag();
+ proxyUid = proxy.getUid();
+ }
+
+ if (accessTime > 0) {
+ pw.print(prefix);
+ pw.print("Access: ");
+ pw.print(AppOpsManager.keyToString(key));
+ pw.print(" ");
+ date.setTime(accessTime);
+ pw.print(sdf.format(date));
+ pw.print(" (");
+ TimeUtils.formatDuration(accessTime - now, pw);
+ pw.print(")");
+ if (accessDuration > 0) {
+ pw.print(" duration=");
+ TimeUtils.formatDuration(accessDuration, pw);
+ }
+ if (proxyUid >= 0) {
+ pw.print(" proxy[");
+ pw.print("uid=");
+ pw.print(proxyUid);
+ pw.print(", pkg=");
+ pw.print(proxyPkg);
+ pw.print(", attributionTag=");
+ pw.print(proxyAttributionTag);
+ pw.print("]");
+ }
+ pw.println();
+ }
+
+ if (rejectTime > 0) {
+ pw.print(prefix);
+ pw.print("Reject: ");
+ pw.print(AppOpsManager.keyToString(key));
+ date.setTime(rejectTime);
+ pw.print(sdf.format(date));
+ pw.print(" (");
+ TimeUtils.formatDuration(rejectTime - now, pw);
+ pw.print(")");
+ if (proxyUid >= 0) {
+ pw.print(" proxy[");
+ pw.print("uid=");
+ pw.print(proxyUid);
+ pw.print(", pkg=");
+ pw.print(proxyPkg);
+ pw.print(", attributionTag=");
+ pw.print(proxyAttributionTag);
+ pw.print("]");
+ }
+ pw.println();
+ }
+ }
+
+ final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
+ if (attributedOp.isRunning()) {
+ long earliestElapsedTime = Long.MAX_VALUE;
+ long maxNumStarts = 0;
+ int numInProgressEvents = attributedOp.mInProgressEvents.size();
+ for (int i = 0; i < numInProgressEvents; i++) {
+ AttributedOp.InProgressStartOpEvent event =
+ attributedOp.mInProgressEvents.valueAt(i);
+
+ earliestElapsedTime = Math.min(earliestElapsedTime, event.getStartElapsedTime());
+ maxNumStarts = Math.max(maxNumStarts, event.mNumUnfinishedStarts);
+ }
+
+ pw.print(prefix + "Running start at: ");
+ TimeUtils.formatDuration(nowElapsed - earliestElapsedTime, pw);
+ pw.println();
+
+ if (maxNumStarts > 1) {
+ pw.print(prefix + "startNesting=");
+ pw.println(maxNumStarts);
+ }
+ }
+ }
+
+ @NeverCompile // Avoid size overhead of debugging code.
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
+
+ int dumpOp = OP_NONE;
+ String dumpPackage = null;
+ String dumpAttributionTag = null;
+ int dumpUid = Process.INVALID_UID;
+ int dumpMode = -1;
+ boolean dumpWatchers = false;
+ // TODO ntmyren: Remove the dumpHistory and dumpFilter
+ boolean dumpHistory = false;
+ boolean includeDiscreteOps = false;
+ boolean dumpUidStateChangeLogs = false;
+ int nDiscreteOps = 10;
+ @HistoricalOpsRequestFilter int dumpFilter = 0;
+ boolean dumpAll = false;
+
+ if (args != null) {
+ for (int i = 0; i < args.length; i++) {
+ String arg = args[i];
+ if ("-h".equals(arg)) {
+ dumpHelp(pw);
+ return;
+ } else if ("-a".equals(arg)) {
+ // dump all data
+ dumpAll = true;
+ } else if ("--op".equals(arg)) {
+ i++;
+ if (i >= args.length) {
+ pw.println("No argument for --op option");
+ return;
+ }
+ dumpOp = Shell.strOpToOp(args[i], pw);
+ dumpFilter |= FILTER_BY_OP_NAMES;
+ if (dumpOp < 0) {
+ return;
+ }
+ } else if ("--package".equals(arg)) {
+ i++;
+ if (i >= args.length) {
+ pw.println("No argument for --package option");
+ return;
+ }
+ dumpPackage = args[i];
+ dumpFilter |= FILTER_BY_PACKAGE_NAME;
+ try {
+ dumpUid = AppGlobals.getPackageManager().getPackageUid(dumpPackage,
+ PackageManager.MATCH_KNOWN_PACKAGES | PackageManager.MATCH_INSTANT,
+ 0);
+ } catch (RemoteException e) {
+ }
+ if (dumpUid < 0) {
+ pw.println("Unknown package: " + dumpPackage);
+ return;
+ }
+ dumpUid = UserHandle.getAppId(dumpUid);
+ dumpFilter |= FILTER_BY_UID;
+ } else if ("--attributionTag".equals(arg)) {
+ i++;
+ if (i >= args.length) {
+ pw.println("No argument for --attributionTag option");
+ return;
+ }
+ dumpAttributionTag = args[i];
+ dumpFilter |= FILTER_BY_ATTRIBUTION_TAG;
+ } else if ("--mode".equals(arg)) {
+ i++;
+ if (i >= args.length) {
+ pw.println("No argument for --mode option");
+ return;
+ }
+ dumpMode = Shell.strModeToMode(args[i], pw);
+ if (dumpMode < 0) {
+ return;
+ }
+ } else if ("--watchers".equals(arg)) {
+ dumpWatchers = true;
+ } else if ("--include-discrete".equals(arg)) {
+ i++;
+ if (i >= args.length) {
+ pw.println("No argument for --include-discrete option");
+ return;
+ }
+ try {
+ nDiscreteOps = Integer.valueOf(args[i]);
+ } catch (NumberFormatException e) {
+ pw.println("Wrong parameter: " + args[i]);
+ return;
+ }
+ includeDiscreteOps = true;
+ } else if ("--history".equals(arg)) {
+ dumpHistory = true;
+ } else if (arg.length() > 0 && arg.charAt(0) == '-') {
+ pw.println("Unknown option: " + arg);
+ return;
+ } else if ("--uid-state-changes".equals(arg)) {
+ dumpUidStateChangeLogs = true;
+ } else {
+ pw.println("Unknown command: " + arg);
+ return;
+ }
+ }
+ }
+
+ final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+ final Date date = new Date();
+ synchronized (this) {
+ pw.println("Current AppOps Service state:");
+ if (!dumpHistory && !dumpWatchers) {
+ mConstants.dump(pw);
+ }
+ pw.println();
+ final long now = System.currentTimeMillis();
+ final long nowElapsed = SystemClock.elapsedRealtime();
+ final long nowUptime = SystemClock.uptimeMillis();
+ boolean needSep = false;
+ if (dumpFilter == 0 && dumpMode < 0 && mProfileOwners != null && !dumpWatchers
+ && !dumpHistory) {
+ pw.println(" Profile owners:");
+ for (int poi = 0; poi < mProfileOwners.size(); poi++) {
+ pw.print(" User #");
+ pw.print(mProfileOwners.keyAt(poi));
+ pw.print(": ");
+ UserHandle.formatUid(pw, mProfileOwners.valueAt(poi));
+ pw.println();
+ }
+ pw.println();
+ }
+
+ if (!dumpHistory) {
+ needSep |= mAppOpsCheckingService.dumpListeners(dumpOp, dumpUid, dumpPackage, pw);
+ }
+
+ if (mModeWatchers.size() > 0 && dumpOp < 0 && !dumpHistory) {
+ boolean printedHeader = false;
+ for (int i = 0; i < mModeWatchers.size(); i++) {
+ final ModeCallback cb = mModeWatchers.valueAt(i);
+ if (dumpPackage != null
+ && dumpUid != UserHandle.getAppId(cb.getWatchingUid())) {
+ continue;
+ }
+ needSep = true;
+ if (!printedHeader) {
+ pw.println(" All op mode watchers:");
+ printedHeader = true;
+ }
+ pw.print(" ");
+ pw.print(Integer.toHexString(System.identityHashCode(mModeWatchers.keyAt(i))));
+ pw.print(": "); pw.println(cb);
+ }
+ }
+ if (mActiveWatchers.size() > 0 && dumpMode < 0) {
+ needSep = true;
+ boolean printedHeader = false;
+ for (int watcherNum = 0; watcherNum < mActiveWatchers.size(); watcherNum++) {
+ final SparseArray<ActiveCallback> activeWatchers =
+ mActiveWatchers.valueAt(watcherNum);
+ if (activeWatchers.size() <= 0) {
+ continue;
+ }
+ final ActiveCallback cb = activeWatchers.valueAt(0);
+ if (dumpOp >= 0 && activeWatchers.indexOfKey(dumpOp) < 0) {
+ continue;
+ }
+ if (dumpPackage != null
+ && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
+ continue;
+ }
+ if (!printedHeader) {
+ pw.println(" All op active watchers:");
+ printedHeader = true;
+ }
+ pw.print(" ");
+ pw.print(Integer.toHexString(System.identityHashCode(
+ mActiveWatchers.keyAt(watcherNum))));
+ pw.println(" ->");
+ pw.print(" [");
+ final int opCount = activeWatchers.size();
+ for (int opNum = 0; opNum < opCount; opNum++) {
+ if (opNum > 0) {
+ pw.print(' ');
+ }
+ pw.print(AppOpsManager.opToName(activeWatchers.keyAt(opNum)));
+ if (opNum < opCount - 1) {
+ pw.print(',');
+ }
+ }
+ pw.println("]");
+ pw.print(" ");
+ pw.println(cb);
+ }
+ }
+ if (mStartedWatchers.size() > 0 && dumpMode < 0) {
+ needSep = true;
+ boolean printedHeader = false;
+
+ final int watchersSize = mStartedWatchers.size();
+ for (int watcherNum = 0; watcherNum < watchersSize; watcherNum++) {
+ final SparseArray<StartedCallback> startedWatchers =
+ mStartedWatchers.valueAt(watcherNum);
+ if (startedWatchers.size() <= 0) {
+ continue;
+ }
+
+ final StartedCallback cb = startedWatchers.valueAt(0);
+ if (dumpOp >= 0 && startedWatchers.indexOfKey(dumpOp) < 0) {
+ continue;
+ }
+
+ if (dumpPackage != null
+ && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
+ continue;
+ }
+
+ if (!printedHeader) {
+ pw.println(" All op started watchers:");
+ printedHeader = true;
+ }
+
+ pw.print(" ");
+ pw.print(Integer.toHexString(System.identityHashCode(
+ mStartedWatchers.keyAt(watcherNum))));
+ pw.println(" ->");
+
+ pw.print(" [");
+ final int opCount = startedWatchers.size();
+ for (int opNum = 0; opNum < opCount; opNum++) {
+ if (opNum > 0) {
+ pw.print(' ');
+ }
+
+ pw.print(AppOpsManager.opToName(startedWatchers.keyAt(opNum)));
+ if (opNum < opCount - 1) {
+ pw.print(',');
+ }
+ }
+ pw.println("]");
+
+ pw.print(" ");
+ pw.println(cb);
+ }
+ }
+ if (mNotedWatchers.size() > 0 && dumpMode < 0) {
+ needSep = true;
+ boolean printedHeader = false;
+ for (int watcherNum = 0; watcherNum < mNotedWatchers.size(); watcherNum++) {
+ final SparseArray<NotedCallback> notedWatchers =
+ mNotedWatchers.valueAt(watcherNum);
+ if (notedWatchers.size() <= 0) {
+ continue;
+ }
+ final NotedCallback cb = notedWatchers.valueAt(0);
+ if (dumpOp >= 0 && notedWatchers.indexOfKey(dumpOp) < 0) {
+ continue;
+ }
+ if (dumpPackage != null
+ && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
+ continue;
+ }
+ if (!printedHeader) {
+ pw.println(" All op noted watchers:");
+ printedHeader = true;
+ }
+ pw.print(" ");
+ pw.print(Integer.toHexString(System.identityHashCode(
+ mNotedWatchers.keyAt(watcherNum))));
+ pw.println(" ->");
+ pw.print(" [");
+ final int opCount = notedWatchers.size();
+ for (int opNum = 0; opNum < opCount; opNum++) {
+ if (opNum > 0) {
+ pw.print(' ');
+ }
+ pw.print(AppOpsManager.opToName(notedWatchers.keyAt(opNum)));
+ if (opNum < opCount - 1) {
+ pw.print(',');
+ }
+ }
+ pw.println("]");
+ pw.print(" ");
+ pw.println(cb);
+ }
+ }
+ if (mAudioRestrictionManager.hasActiveRestrictions() && dumpOp < 0
+ && dumpPackage != null && dumpMode < 0 && !dumpWatchers) {
+ needSep = mAudioRestrictionManager.dump(pw) || needSep;
+ }
+ if (needSep) {
+ pw.println();
+ }
+ for (int i=0; i<mUidStates.size(); i++) {
+ UidState uidState = mUidStates.valueAt(i);
+ final SparseIntArray opModes = uidState.getNonDefaultUidModes();
+ final ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
+
+ if (dumpWatchers || dumpHistory) {
+ continue;
+ }
+ if (dumpOp >= 0 || dumpPackage != null || dumpMode >= 0) {
+ boolean hasOp = dumpOp < 0 || (opModes != null
+ && opModes.indexOfKey(dumpOp) >= 0);
+ boolean hasPackage = dumpPackage == null || dumpUid == mUidStates.keyAt(i);
+ boolean hasMode = dumpMode < 0;
+ if (!hasMode && opModes != null) {
+ for (int opi = 0; !hasMode && opi < opModes.size(); opi++) {
+ if (opModes.valueAt(opi) == dumpMode) {
+ hasMode = true;
+ }
+ }
+ }
+ if (pkgOps != null) {
+ for (int pkgi = 0;
+ (!hasOp || !hasPackage || !hasMode) && pkgi < pkgOps.size();
+ pkgi++) {
+ Ops ops = pkgOps.valueAt(pkgi);
+ if (!hasOp && ops != null && ops.indexOfKey(dumpOp) >= 0) {
+ hasOp = true;
+ }
+ if (!hasMode) {
+ for (int opi = 0; !hasMode && opi < ops.size(); opi++) {
+ if (ops.valueAt(opi).getMode() == dumpMode) {
+ hasMode = true;
+ }
+ }
+ }
+ if (!hasPackage && dumpPackage.equals(ops.packageName)) {
+ hasPackage = true;
+ }
+ }
+ }
+ if (uidState.foregroundOps != null && !hasOp) {
+ if (uidState.foregroundOps.indexOfKey(dumpOp) > 0) {
+ hasOp = true;
+ }
+ }
+ if (!hasOp || !hasPackage || !hasMode) {
+ continue;
+ }
+ }
+
+ pw.print(" Uid "); UserHandle.formatUid(pw, uidState.uid); pw.println(":");
+ uidState.dump(pw, nowElapsed);
+ if (uidState.foregroundOps != null && (dumpMode < 0
+ || dumpMode == AppOpsManager.MODE_FOREGROUND)) {
+ pw.println(" foregroundOps:");
+ for (int j = 0; j < uidState.foregroundOps.size(); j++) {
+ if (dumpOp >= 0 && dumpOp != uidState.foregroundOps.keyAt(j)) {
+ continue;
+ }
+ pw.print(" ");
+ pw.print(AppOpsManager.opToName(uidState.foregroundOps.keyAt(j)));
+ pw.print(": ");
+ pw.println(uidState.foregroundOps.valueAt(j) ? "WATCHER" : "SILENT");
+ }
+ pw.print(" hasForegroundWatchers=");
+ pw.println(uidState.hasForegroundWatchers);
+ }
+ needSep = true;
+
+ if (opModes != null) {
+ final int opModeCount = opModes.size();
+ for (int j = 0; j < opModeCount; j++) {
+ final int code = opModes.keyAt(j);
+ final int mode = opModes.valueAt(j);
+ if (dumpOp >= 0 && dumpOp != code) {
+ continue;
+ }
+ if (dumpMode >= 0 && dumpMode != mode) {
+ continue;
+ }
+ pw.print(" "); pw.print(AppOpsManager.opToName(code));
+ pw.print(": mode="); pw.println(AppOpsManager.modeToName(mode));
+ }
+ }
+
+ if (pkgOps == null) {
+ continue;
+ }
+
+ for (int pkgi = 0; pkgi < pkgOps.size(); pkgi++) {
+ final Ops ops = pkgOps.valueAt(pkgi);
+ if (dumpPackage != null && !dumpPackage.equals(ops.packageName)) {
+ continue;
+ }
+ boolean printedPackage = false;
+ for (int j=0; j<ops.size(); j++) {
+ final Op op = ops.valueAt(j);
+ final int opCode = op.op;
+ if (dumpOp >= 0 && dumpOp != opCode) {
+ continue;
+ }
+ if (dumpMode >= 0 && dumpMode != op.getMode()) {
+ continue;
+ }
+ if (!printedPackage) {
+ pw.print(" Package "); pw.print(ops.packageName); pw.println(":");
+ printedPackage = true;
+ }
+ pw.print(" "); pw.print(AppOpsManager.opToName(opCode));
+ pw.print(" ("); pw.print(AppOpsManager.modeToName(op.getMode()));
+ final int switchOp = AppOpsManager.opToSwitch(opCode);
+ if (switchOp != opCode) {
+ pw.print(" / switch ");
+ pw.print(AppOpsManager.opToName(switchOp));
+ final Op switchObj = ops.get(switchOp);
+ int mode = switchObj == null
+ ? AppOpsManager.opToDefaultMode(switchOp) : switchObj.getMode();
+ pw.print("="); pw.print(AppOpsManager.modeToName(mode));
+ }
+ pw.println("): ");
+ dumpStatesLocked(pw, dumpAttributionTag, dumpFilter, nowElapsed, op, now,
+ sdf, date, " ");
+ }
+ }
+ }
+ if (needSep) {
+ pw.println();
+ }
+
+ boolean showUserRestrictions = !(dumpMode < 0 && !dumpWatchers && !dumpHistory);
+ mAppOpsRestrictions.dumpRestrictions(pw, dumpOp, dumpPackage, showUserRestrictions);
+
+ if (!dumpHistory && !dumpWatchers) {
+ pw.println();
+ if (mCheckOpsDelegateDispatcher.mPolicy != null
+ && mCheckOpsDelegateDispatcher.mPolicy instanceof AppOpsPolicy) {
+ AppOpsPolicy policy = (AppOpsPolicy) mCheckOpsDelegateDispatcher.mPolicy;
+ policy.dumpTags(pw);
+ } else {
+ pw.println(" AppOps policy not set.");
+ }
+ }
+
+ if (dumpAll || dumpUidStateChangeLogs) {
+ pw.println();
+ pw.println("Uid State Changes Event Log:");
+ getUidStateTracker().dumpEvents(pw);
+ }
+ }
+
+ // Must not hold the appops lock
+ if (dumpHistory && !dumpWatchers) {
+ mHistoricalRegistry.dump(" ", pw, dumpUid, dumpPackage, dumpAttributionTag, dumpOp,
+ dumpFilter);
+ }
+ if (includeDiscreteOps) {
+ pw.println("Discrete accesses: ");
+ mHistoricalRegistry.dumpDiscreteData(pw, dumpUid, dumpPackage, dumpAttributionTag,
+ dumpFilter, dumpOp, sdf, date, " ", nDiscreteOps);
+ }
+ }
+
@Override
public void setUserRestrictions(Bundle restrictions, IBinder token, int userHandle) {
- mAppOpsService.setUserRestrictions(restrictions, token, userHandle);
+ checkSystemUid("setUserRestrictions");
+ Objects.requireNonNull(restrictions);
+ Objects.requireNonNull(token);
+ for (int i = 0; i < AppOpsManager._NUM_OP; i++) {
+ String restriction = AppOpsManager.opToRestriction(i);
+ if (restriction != null) {
+ setUserRestrictionNoCheck(i, restrictions.getBoolean(restriction, false), token,
+ userHandle, null);
+ }
+ }
}
@Override
public void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle,
PackageTagsList excludedPackageTags) {
- mAppOpsService.setUserRestriction(code, restricted, token, userHandle,
- excludedPackageTags);
+ if (Binder.getCallingPid() != Process.myPid()) {
+ mContext.enforcePermission(Manifest.permission.MANAGE_APP_OPS_RESTRICTIONS,
+ Binder.getCallingPid(), Binder.getCallingUid(), null);
+ }
+ if (userHandle != UserHandle.getCallingUserId()) {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission
+ .INTERACT_ACROSS_USERS_FULL) != PackageManager.PERMISSION_GRANTED
+ && mContext.checkCallingOrSelfPermission(Manifest.permission
+ .INTERACT_ACROSS_USERS) != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Need INTERACT_ACROSS_USERS_FULL or"
+ + " INTERACT_ACROSS_USERS to interact cross user ");
+ }
+ }
+ verifyIncomingOp(code);
+ Objects.requireNonNull(token);
+ setUserRestrictionNoCheck(code, restricted, token, userHandle, excludedPackageTags);
+ }
+
+ private void setUserRestrictionNoCheck(int code, boolean restricted, IBinder token,
+ int userHandle, PackageTagsList excludedPackageTags) {
+ synchronized (AppOpsService.this) {
+ ClientUserRestrictionState restrictionState = mOpUserRestrictions.get(token);
+
+ if (restrictionState == null) {
+ try {
+ restrictionState = new ClientUserRestrictionState(token);
+ } catch (RemoteException e) {
+ return;
+ }
+ mOpUserRestrictions.put(token, restrictionState);
+ }
+
+ if (restrictionState.setRestriction(code, restricted, excludedPackageTags,
+ userHandle)) {
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsService::notifyWatchersOfChange, this, code, UID_ANY));
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsService::updateStartedOpModeForUser, this, code, restricted,
+ userHandle));
+ }
+
+ if (restrictionState.isDefault()) {
+ mOpUserRestrictions.remove(token);
+ restrictionState.destroy();
+ }
+ }
+ }
+
+ private void updateStartedOpModeForUser(int code, boolean restricted, int userId) {
+ synchronized (AppOpsService.this) {
+ int numUids = mUidStates.size();
+ for (int uidNum = 0; uidNum < numUids; uidNum++) {
+ int uid = mUidStates.keyAt(uidNum);
+ if (userId != UserHandle.USER_ALL && UserHandle.getUserId(uid) != userId) {
+ continue;
+ }
+ updateStartedOpModeForUidLocked(code, restricted, uid);
+ }
+ }
+ }
+
+ private void updateStartedOpModeForUidLocked(int code, boolean restricted, int uid) {
+ UidState uidState = mUidStates.get(uid);
+ if (uidState == null || uidState.pkgOps == null) {
+ return;
+ }
+
+ int numPkgOps = uidState.pkgOps.size();
+ for (int pkgNum = 0; pkgNum < numPkgOps; pkgNum++) {
+ Ops ops = uidState.pkgOps.valueAt(pkgNum);
+ Op op = ops != null ? ops.get(code) : null;
+ if (op == null || (op.getMode() != MODE_ALLOWED && op.getMode() != MODE_FOREGROUND)) {
+ continue;
+ }
+ int numAttrTags = op.mAttributions.size();
+ for (int attrNum = 0; attrNum < numAttrTags; attrNum++) {
+ AttributedOp attrOp = op.mAttributions.valueAt(attrNum);
+ if (restricted && attrOp.isRunning()) {
+ attrOp.pause();
+ } else if (attrOp.isPaused()) {
+ attrOp.resume();
+ }
+ }
+ }
+ }
+
+ private void notifyWatchersOfChange(int code, int uid) {
+ final ArraySet<OnOpModeChangedListener> modeChangedListenerSet;
+ synchronized (this) {
+ modeChangedListenerSet = mAppOpsCheckingService.getOpModeChangedListeners(code);
+ if (modeChangedListenerSet == null) {
+ return;
+ }
+ }
+
+ notifyOpChanged(modeChangedListenerSet, code, uid, null);
}
@Override
public void removeUser(int userHandle) throws RemoteException {
- mAppOpsService.removeUser(userHandle);
+ checkSystemUid("removeUser");
+ synchronized (AppOpsService.this) {
+ final int tokenCount = mOpUserRestrictions.size();
+ for (int i = tokenCount - 1; i >= 0; i--) {
+ ClientUserRestrictionState opRestrictions = mOpUserRestrictions.valueAt(i);
+ opRestrictions.removeUser(userHandle);
+ }
+ removeUidsForUserLocked(userHandle);
+ }
}
@Override
public boolean isOperationActive(int code, int uid, String packageName) {
- return mAppOpsService.isOperationActive(code, uid, packageName);
+ if (Binder.getCallingUid() != uid) {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
+ != PackageManager.PERMISSION_GRANTED) {
+ return false;
+ }
+ }
+ verifyIncomingOp(code);
+ if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+ return false;
+ }
+
+ final String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+ if (resolvedPackageName == null) {
+ return false;
+ }
+ // TODO moltmann: Allow to check for attribution op activeness
+ synchronized (AppOpsService.this) {
+ Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, false, null, false);
+ if (pkgOps == null) {
+ return false;
+ }
+
+ Op op = pkgOps.get(code);
+ if (op == null) {
+ return false;
+ }
+
+ return op.isRunning();
+ }
}
@Override
public boolean isProxying(int op, @NonNull String proxyPackageName,
@NonNull String proxyAttributionTag, int proxiedUid,
@NonNull String proxiedPackageName) {
- return mAppOpsService.isProxying(op, proxyPackageName, proxyAttributionTag,
- proxiedUid, proxiedPackageName);
+ Objects.requireNonNull(proxyPackageName);
+ Objects.requireNonNull(proxiedPackageName);
+ final long callingUid = Binder.getCallingUid();
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ final List<AppOpsManager.PackageOps> packageOps = getOpsForPackage(proxiedUid,
+ proxiedPackageName, new int[] {op});
+ if (packageOps == null || packageOps.isEmpty()) {
+ return false;
+ }
+ final List<OpEntry> opEntries = packageOps.get(0).getOps();
+ if (opEntries.isEmpty()) {
+ return false;
+ }
+ final OpEntry opEntry = opEntries.get(0);
+ if (!opEntry.isRunning()) {
+ return false;
+ }
+ final OpEventProxyInfo proxyInfo = opEntry.getLastProxyInfo(
+ OP_FLAG_TRUSTED_PROXIED | AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED);
+ return proxyInfo != null && callingUid == proxyInfo.getUid()
+ && proxyPackageName.equals(proxyInfo.getPackageName())
+ && Objects.equals(proxyAttributionTag, proxyInfo.getAttributionTag());
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
@Override
public void resetPackageOpsNoHistory(@NonNull String packageName) {
- mAppOpsService.resetPackageOpsNoHistory(packageName);
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+ "resetPackageOpsNoHistory");
+ synchronized (AppOpsService.this) {
+ final int uid = mPackageManagerInternal.getPackageUid(packageName, 0,
+ UserHandle.getCallingUserId());
+ if (uid == Process.INVALID_UID) {
+ return;
+ }
+ UidState uidState = mUidStates.get(uid);
+ if (uidState == null || uidState.pkgOps == null) {
+ return;
+ }
+ Ops removedOps = uidState.pkgOps.remove(packageName);
+ mAppOpsCheckingService.removePackage(packageName, UserHandle.getUserId(uid));
+ if (removedOps != null) {
+ scheduleFastWriteLocked();
+ }
+ }
}
@Override
public void setHistoryParameters(@AppOpsManager.HistoricalMode int mode,
long baseSnapshotInterval, int compressionStep) {
- mAppOpsService.setHistoryParameters(mode, baseSnapshotInterval, compressionStep);
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+ "setHistoryParameters");
+ // Must not hold the appops lock
+ mHistoricalRegistry.setHistoryParameters(mode, baseSnapshotInterval, compressionStep);
}
@Override
public void offsetHistory(long offsetMillis) {
- mAppOpsService.offsetHistory(offsetMillis);
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+ "offsetHistory");
+ // Must not hold the appops lock
+ mHistoricalRegistry.offsetHistory(offsetMillis);
+ mHistoricalRegistry.offsetDiscreteHistory(offsetMillis);
}
@Override
public void addHistoricalOps(HistoricalOps ops) {
- mAppOpsService.addHistoricalOps(ops);
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+ "addHistoricalOps");
+ // Must not hold the appops lock
+ mHistoricalRegistry.addHistoricalOps(ops);
}
@Override
public void resetHistoryParameters() {
- mAppOpsService.resetHistoryParameters();
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+ "resetHistoryParameters");
+ // Must not hold the appops lock
+ mHistoricalRegistry.resetHistoryParameters();
}
@Override
public void clearHistory() {
- mAppOpsService.clearHistory();
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+ "clearHistory");
+ // Must not hold the appops lock
+ mHistoricalRegistry.clearAllHistory();
}
@Override
public void rebootHistory(long offlineDurationMillis) {
- mAppOpsService.rebootHistory(offlineDurationMillis);
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+ "rebootHistory");
+
+ Preconditions.checkArgument(offlineDurationMillis >= 0);
+
+ // Must not hold the appops lock
+ mHistoricalRegistry.shutdown();
+
+ if (offlineDurationMillis > 0) {
+ SystemClock.sleep(offlineDurationMillis);
+ }
+
+ mHistoricalRegistry = new HistoricalRegistry(mHistoricalRegistry);
+ mHistoricalRegistry.systemReady(mContext.getContentResolver());
+ mHistoricalRegistry.persistPendingHistory();
}
/**
@@ -1998,6 +5982,24 @@ public class AppOpsService extends IAppOpsService.Stub {
return false;
}
+ @GuardedBy("this")
+ private void removeUidsForUserLocked(int userHandle) {
+ for (int i = mUidStates.size() - 1; i >= 0; --i) {
+ final int uid = mUidStates.keyAt(i);
+ if (UserHandle.getUserId(uid) == userHandle) {
+ mUidStates.valueAt(i).clear();
+ mUidStates.removeAt(i);
+ }
+ }
+ }
+
+ private void checkSystemUid(String function) {
+ int uid = Binder.getCallingUid();
+ if (uid != Process.SYSTEM_UID) {
+ throw new SecurityException(function + " must by called by the system");
+ }
+ }
+
private static int resolveUid(String packageName) {
if (packageName == null) {
return Process.INVALID_UID;
@@ -2018,43 +6020,184 @@ public class AppOpsService extends IAppOpsService.Stub {
return Process.INVALID_UID;
}
+ private static String[] getPackagesForUid(int uid) {
+ String[] packageNames = null;
+
+ // Very early during boot the package manager is not yet or not yet fully started. At this
+ // time there are no packages yet.
+ if (AppGlobals.getPackageManager() != null) {
+ try {
+ packageNames = AppGlobals.getPackageManager().getPackagesForUid(uid);
+ } catch (RemoteException e) {
+ /* ignore - local call */
+ }
+ }
+ if (packageNames == null) {
+ return EmptyArray.STRING;
+ }
+ return packageNames;
+ }
+
+ private final class ClientUserRestrictionState implements DeathRecipient {
+ private final IBinder token;
+
+ ClientUserRestrictionState(IBinder token)
+ throws RemoteException {
+ token.linkToDeath(this, 0);
+ this.token = token;
+ }
+
+ public boolean setRestriction(int code, boolean restricted,
+ PackageTagsList excludedPackageTags, int userId) {
+ return mAppOpsRestrictions.setUserRestriction(token, userId, code,
+ restricted, excludedPackageTags);
+ }
+
+ public boolean hasRestriction(int code, String packageName, String attributionTag,
+ int userId, boolean isCheckOp) {
+ return mAppOpsRestrictions.getUserRestriction(token, userId, code, packageName,
+ attributionTag, isCheckOp);
+ }
+
+ public void removeUser(int userId) {
+ mAppOpsRestrictions.clearUserRestrictions(token, userId);
+ }
+
+ public boolean isDefault() {
+ return !mAppOpsRestrictions.hasUserRestrictions(token);
+ }
+
+ @Override
+ public void binderDied() {
+ synchronized (AppOpsService.this) {
+ mAppOpsRestrictions.clearUserRestrictions(token);
+ mOpUserRestrictions.remove(token);
+ destroy();
+ }
+ }
+
+ public void destroy() {
+ token.unlinkToDeath(this, 0);
+ }
+ }
+
+ private final class ClientGlobalRestrictionState implements DeathRecipient {
+ final IBinder mToken;
+
+ ClientGlobalRestrictionState(IBinder token)
+ throws RemoteException {
+ token.linkToDeath(this, 0);
+ this.mToken = token;
+ }
+
+ boolean setRestriction(int code, boolean restricted) {
+ return mAppOpsRestrictions.setGlobalRestriction(mToken, code, restricted);
+ }
+
+ boolean hasRestriction(int code) {
+ return mAppOpsRestrictions.getGlobalRestriction(mToken, code);
+ }
+
+ boolean isDefault() {
+ return !mAppOpsRestrictions.hasGlobalRestrictions(mToken);
+ }
+
+ @Override
+ public void binderDied() {
+ mAppOpsRestrictions.clearGlobalRestrictions(mToken);
+ mOpGlobalRestrictions.remove(mToken);
+ destroy();
+ }
+
+ void destroy() {
+ mToken.unlinkToDeath(this, 0);
+ }
+ }
+
private final class AppOpsManagerInternalImpl extends AppOpsManagerInternal {
@Override public void setDeviceAndProfileOwners(SparseIntArray owners) {
- AppOpsService.this.mAppOpsService.setDeviceAndProfileOwners(owners);
+ synchronized (AppOpsService.this) {
+ mProfileOwners = owners;
+ }
}
@Override
public void updateAppWidgetVisibility(SparseArray<String> uidPackageNames,
boolean visible) {
- AppOpsService.this.mAppOpsService
- .updateAppWidgetVisibility(uidPackageNames, visible);
+ AppOpsService.this.updateAppWidgetVisibility(uidPackageNames, visible);
}
@Override
public void setUidModeFromPermissionPolicy(int code, int uid, int mode,
@Nullable IAppOpsCallback callback) {
- AppOpsService.this.mAppOpsService.setUidMode(code, uid, mode, callback);
+ setUidMode(code, uid, mode, callback);
}
@Override
public void setModeFromPermissionPolicy(int code, int uid, @NonNull String packageName,
int mode, @Nullable IAppOpsCallback callback) {
- AppOpsService.this.mAppOpsService
- .setMode(code, uid, packageName, mode, callback);
+ setMode(code, uid, packageName, mode, callback);
}
@Override
public void setGlobalRestriction(int code, boolean restricted, IBinder token) {
- AppOpsService.this.mAppOpsService
- .setGlobalRestriction(code, restricted, token);
+ if (Binder.getCallingPid() != Process.myPid()) {
+ // TODO instead of this enforcement put in AppOpsManagerInternal
+ throw new SecurityException("Only the system can set global restrictions");
+ }
+
+ synchronized (AppOpsService.this) {
+ ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.get(token);
+
+ if (restrictionState == null) {
+ try {
+ restrictionState = new ClientGlobalRestrictionState(token);
+ } catch (RemoteException e) {
+ return;
+ }
+ mOpGlobalRestrictions.put(token, restrictionState);
+ }
+
+ if (restrictionState.setRestriction(code, restricted)) {
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsService::notifyWatchersOfChange, AppOpsService.this, code,
+ UID_ANY));
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsService::updateStartedOpModeForUser, AppOpsService.this,
+ code, restricted, UserHandle.USER_ALL));
+ }
+
+ if (restrictionState.isDefault()) {
+ mOpGlobalRestrictions.remove(token);
+ restrictionState.destroy();
+ }
+ }
}
@Override
public int getOpRestrictionCount(int code, UserHandle user, String pkg,
String attributionTag) {
- return AppOpsService.this.mAppOpsService
- .getOpRestrictionCount(code, user, pkg, attributionTag);
+ int number = 0;
+ synchronized (AppOpsService.this) {
+ int numRestrictions = mOpUserRestrictions.size();
+ for (int i = 0; i < numRestrictions; i++) {
+ if (mOpUserRestrictions.valueAt(i)
+ .hasRestriction(code, pkg, attributionTag, user.getIdentifier(),
+ false)) {
+ number++;
+ }
+ }
+
+ numRestrictions = mOpGlobalRestrictions.size();
+ for (int i = 0; i < numRestrictions; i++) {
+ if (mOpGlobalRestrictions.valueAt(i).hasRestriction(code)) {
+ number++;
+ }
+ }
+ }
+
+ return number;
}
}
@@ -2350,7 +6493,7 @@ public class AppOpsService extends IAppOpsService.Stub {
attributionFlags, attributionChainId, AppOpsService.this::startOperationImpl);
}
- public SyncNotedAppOp startProxyOperation(IBinder clientId, int code,
+ public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean startIfModeDefault,
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
@@ -2380,7 +6523,7 @@ public class AppOpsService extends IAppOpsService.Stub {
proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
}
- private SyncNotedAppOp startDelegateProxyOperationImpl(IBinder clientId, int code,
+ private SyncNotedAppOp startDelegateProxyOperationImpl(@NonNull IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean startIfModeDefault,
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
@@ -2414,7 +6557,7 @@ public class AppOpsService extends IAppOpsService.Stub {
AppOpsService.this::finishOperationImpl);
}
- public void finishProxyOperation(IBinder clientId, int code,
+ public void finishProxyOperation(@NonNull IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
if (mPolicy != null) {
if (mCheckOpsDelegate != null) {
@@ -2432,7 +6575,7 @@ public class AppOpsService extends IAppOpsService.Stub {
}
}
- private Void finishDelegateProxyOperationImpl(IBinder clientId, int code,
+ private Void finishDelegateProxyOperationImpl(@NonNull IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
mCheckOpsDelegate.finishProxyOperation(clientId, code, attributionSource,
skipProxyOperation, AppOpsService.this::finishProxyOperationImpl);
diff --git a/services/core/java/com/android/server/appop/AppOpsServiceImpl.java b/services/core/java/com/android/server/appop/AppOpsServiceImpl.java
deleted file mode 100644
index c3d2717ce795..000000000000
--- a/services/core/java/com/android/server/appop/AppOpsServiceImpl.java
+++ /dev/null
@@ -1,4742 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.appop;
-
-import static android.app.AppOpsManager.AttributedOpEntry;
-import static android.app.AppOpsManager.AttributionFlags;
-import static android.app.AppOpsManager.CALL_BACK_ON_SWITCHED_OP;
-import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG;
-import static android.app.AppOpsManager.FILTER_BY_OP_NAMES;
-import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME;
-import static android.app.AppOpsManager.FILTER_BY_UID;
-import static android.app.AppOpsManager.HISTORY_FLAG_GET_ATTRIBUTION_CHAINS;
-import static android.app.AppOpsManager.HistoricalOps;
-import static android.app.AppOpsManager.HistoricalOpsRequestFilter;
-import static android.app.AppOpsManager.KEY_BG_STATE_SETTLE_TIME;
-import static android.app.AppOpsManager.KEY_FG_SERVICE_STATE_SETTLE_TIME;
-import static android.app.AppOpsManager.KEY_TOP_STATE_SETTLE_TIME;
-import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.app.AppOpsManager.MODE_DEFAULT;
-import static android.app.AppOpsManager.MODE_ERRORED;
-import static android.app.AppOpsManager.MODE_FOREGROUND;
-import static android.app.AppOpsManager.MODE_IGNORED;
-import static android.app.AppOpsManager.Mode;
-import static android.app.AppOpsManager.OP_CAMERA;
-import static android.app.AppOpsManager.OP_FLAGS_ALL;
-import static android.app.AppOpsManager.OP_FLAG_SELF;
-import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
-import static android.app.AppOpsManager.OP_NONE;
-import static android.app.AppOpsManager.OP_PLAY_AUDIO;
-import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
-import static android.app.AppOpsManager.OP_RECORD_AUDIO;
-import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
-import static android.app.AppOpsManager.OP_SCHEDULE_EXACT_ALARM;
-import static android.app.AppOpsManager.OP_VIBRATE;
-import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED;
-import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED;
-import static android.app.AppOpsManager.OpEntry;
-import static android.app.AppOpsManager.OpEventProxyInfo;
-import static android.app.AppOpsManager.OpFlags;
-import static android.app.AppOpsManager.RestrictionBypass;
-import static android.app.AppOpsManager.SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE;
-import static android.app.AppOpsManager._NUM_OP;
-import static android.app.AppOpsManager.extractFlagsFromKey;
-import static android.app.AppOpsManager.extractUidStateFromKey;
-import static android.app.AppOpsManager.modeToName;
-import static android.app.AppOpsManager.opAllowSystemBypassRestriction;
-import static android.app.AppOpsManager.opRestrictsRead;
-import static android.app.AppOpsManager.opToName;
-import static android.content.Intent.ACTION_PACKAGE_REMOVED;
-import static android.content.Intent.EXTRA_REPLACING;
-
-import static com.android.server.appop.AppOpsServiceImpl.ModeCallback.ALL_OPS;
-
-import android.Manifest;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.app.ActivityManager;
-import android.app.ActivityManagerInternal;
-import android.app.AppGlobals;
-import android.app.AppOpsManager;
-import android.app.admin.DevicePolicyManagerInternal;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
-import android.content.pm.PermissionInfo;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Binder;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerExecutor;
-import android.os.IBinder;
-import android.os.IBinder.DeathRecipient;
-import android.os.PackageTagsList;
-import android.os.Process;
-import android.os.RemoteCallback;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.os.storage.StorageManagerInternal;
-import android.permission.PermissionManager;
-import android.provider.Settings;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.AtomicFile;
-import android.util.KeyValueListParser;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
-import android.util.TimeUtils;
-import android.util.Xml;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.IAppOpsActiveCallback;
-import com.android.internal.app.IAppOpsCallback;
-import com.android.internal.app.IAppOpsNotedCallback;
-import com.android.internal.app.IAppOpsStartedCallback;
-import com.android.internal.compat.IPlatformCompat;
-import com.android.internal.os.Clock;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.DumpUtils;
-import com.android.internal.util.Preconditions;
-import com.android.internal.util.XmlUtils;
-import com.android.internal.util.function.pooled.PooledLambda;
-import com.android.modules.utils.TypedXmlPullParser;
-import com.android.modules.utils.TypedXmlSerializer;
-import com.android.server.LocalServices;
-import com.android.server.LockGuard;
-import com.android.server.SystemServerInitThreadPool;
-import com.android.server.pm.UserManagerInternal;
-import com.android.server.pm.permission.PermissionManagerServiceInternal;
-import com.android.server.pm.pkg.AndroidPackage;
-import com.android.server.pm.pkg.component.ParsedAttribution;
-
-import dalvik.annotation.optimization.NeverCompile;
-
-import libcore.util.EmptyArray;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-
-class AppOpsServiceImpl implements AppOpsServiceInterface {
- static final String TAG = "AppOps";
- static final boolean DEBUG = false;
-
- /**
- * Sentinel integer version to denote that there was no appops.xml found on boot.
- * This will happen when a device boots with no existing userdata.
- */
- private static final int NO_FILE_VERSION = -2;
-
- /**
- * Sentinel integer version to denote that there was no version in the appops.xml found on boot.
- * This means the file is coming from a build before versioning was added.
- */
- private static final int NO_VERSION = -1;
-
- /**
- * Increment by one every time and add the corresponding upgrade logic in
- * {@link #upgradeLocked(int)} below. The first version was 1.
- */
- @VisibleForTesting
- static final int CURRENT_VERSION = 2;
-
- /**
- * This stores the version of appops.xml seen at boot. If this is smaller than
- * {@link #CURRENT_VERSION}, then we will run {@link #upgradeLocked(int)} on startup.
- */
- private int mVersionAtBoot = NO_FILE_VERSION;
-
- // Write at most every 30 minutes.
- static final long WRITE_DELAY = DEBUG ? 1000 : 30 * 60 * 1000;
-
- // Constant meaning that any UID should be matched when dispatching callbacks
- private static final int UID_ANY = -2;
-
- private static final int[] OPS_RESTRICTED_ON_SUSPEND = {
- OP_PLAY_AUDIO,
- OP_RECORD_AUDIO,
- OP_CAMERA,
- OP_VIBRATE,
- };
- private static final int MAX_UNUSED_POOLED_OBJECTS = 3;
-
- final Context mContext;
- final AtomicFile mFile;
- final Handler mHandler;
-
- /**
- * Pool for {@link AttributedOp.OpEventProxyInfoPool} to avoid to constantly reallocate new
- * objects
- */
- @GuardedBy("this")
- final AttributedOp.OpEventProxyInfoPool mOpEventProxyInfoPool =
- new AttributedOp.OpEventProxyInfoPool(MAX_UNUSED_POOLED_OBJECTS);
-
- /**
- * Pool for {@link AttributedOp.InProgressStartOpEventPool} to avoid to constantly reallocate
- * new objects
- */
- @GuardedBy("this")
- final AttributedOp.InProgressStartOpEventPool mInProgressStartOpEventPool =
- new AttributedOp.InProgressStartOpEventPool(mOpEventProxyInfoPool,
- MAX_UNUSED_POOLED_OBJECTS);
- @Nullable
- private final DevicePolicyManagerInternal dpmi =
- LocalServices.getService(DevicePolicyManagerInternal.class);
-
- private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface(
- ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
-
- boolean mWriteScheduled;
- boolean mFastWriteScheduled;
- final Runnable mWriteRunner = new Runnable() {
- public void run() {
- synchronized (AppOpsServiceImpl.this) {
- mWriteScheduled = false;
- mFastWriteScheduled = false;
- AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- writeState();
- return null;
- }
- };
- task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
- }
- }
- };
-
- @GuardedBy("this")
- @VisibleForTesting
- final SparseArray<UidState> mUidStates = new SparseArray<>();
-
- volatile @NonNull HistoricalRegistry mHistoricalRegistry = new HistoricalRegistry(this);
-
- /*
- * These are app op restrictions imposed per user from various parties.
- */
- private final ArrayMap<IBinder, ClientUserRestrictionState> mOpUserRestrictions =
- new ArrayMap<>();
-
- /*
- * These are app op restrictions imposed globally from various parties within the system.
- */
- private final ArrayMap<IBinder, ClientGlobalRestrictionState> mOpGlobalRestrictions =
- new ArrayMap<>();
-
- SparseIntArray mProfileOwners;
-
- /**
- * Reverse lookup for {@link AppOpsManager#opToSwitch(int)}. Initialized once and never
- * changed
- */
- private final SparseArray<int[]> mSwitchedOps = new SparseArray<>();
-
- /**
- * Package Manager internal. Access via {@link #getPackageManagerInternal()}
- */
- private @Nullable PackageManagerInternal mPackageManagerInternal;
-
- /**
- * Interface for app-op modes.
- */
- @VisibleForTesting
- AppOpsCheckingServiceInterface mAppOpsServiceInterface;
-
- /**
- * Interface for app-op restrictions.
- */
- @VisibleForTesting
- AppOpsRestrictions mAppOpsRestrictions;
-
- private AppOpsUidStateTracker mUidStateTracker;
-
- /**
- * Hands the definition of foreground and uid states
- */
- @GuardedBy("this")
- public AppOpsUidStateTracker getUidStateTracker() {
- if (mUidStateTracker == null) {
- mUidStateTracker = new AppOpsUidStateTrackerImpl(
- LocalServices.getService(ActivityManagerInternal.class),
- mHandler,
- r -> {
- synchronized (AppOpsServiceImpl.this) {
- r.run();
- }
- },
- Clock.SYSTEM_CLOCK, mConstants);
-
- mUidStateTracker.addUidStateChangedCallback(new HandlerExecutor(mHandler),
- this::onUidStateChanged);
- }
- return mUidStateTracker;
- }
-
- /**
- * All times are in milliseconds. These constants are kept synchronized with the system
- * global Settings. Any access to this class or its fields should be done while
- * holding the AppOpsService lock.
- */
- final class Constants extends ContentObserver {
-
- /**
- * How long we want for a drop in uid state from top to settle before applying it.
- *
- * @see Settings.Global#APP_OPS_CONSTANTS
- * @see AppOpsManager#KEY_TOP_STATE_SETTLE_TIME
- */
- public long TOP_STATE_SETTLE_TIME;
-
- /**
- * How long we want for a drop in uid state from foreground to settle before applying it.
- *
- * @see Settings.Global#APP_OPS_CONSTANTS
- * @see AppOpsManager#KEY_FG_SERVICE_STATE_SETTLE_TIME
- */
- public long FG_SERVICE_STATE_SETTLE_TIME;
-
- /**
- * How long we want for a drop in uid state from background to settle before applying it.
- *
- * @see Settings.Global#APP_OPS_CONSTANTS
- * @see AppOpsManager#KEY_BG_STATE_SETTLE_TIME
- */
- public long BG_STATE_SETTLE_TIME;
-
- private final KeyValueListParser mParser = new KeyValueListParser(',');
- private ContentResolver mResolver;
-
- Constants(Handler handler) {
- super(handler);
- updateConstants();
- }
-
- public void startMonitoring(ContentResolver resolver) {
- mResolver = resolver;
- mResolver.registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.APP_OPS_CONSTANTS),
- false, this);
- updateConstants();
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- updateConstants();
- }
-
- private void updateConstants() {
- String value = mResolver != null ? Settings.Global.getString(mResolver,
- Settings.Global.APP_OPS_CONSTANTS) : "";
-
- synchronized (AppOpsServiceImpl.this) {
- try {
- mParser.setString(value);
- } catch (IllegalArgumentException e) {
- // Failed to parse the settings string, log this and move on
- // with defaults.
- Slog.e(TAG, "Bad app ops settings", e);
- }
- TOP_STATE_SETTLE_TIME = mParser.getDurationMillis(
- KEY_TOP_STATE_SETTLE_TIME, 5 * 1000L);
- FG_SERVICE_STATE_SETTLE_TIME = mParser.getDurationMillis(
- KEY_FG_SERVICE_STATE_SETTLE_TIME, 5 * 1000L);
- BG_STATE_SETTLE_TIME = mParser.getDurationMillis(
- KEY_BG_STATE_SETTLE_TIME, 1 * 1000L);
- }
- }
-
- void dump(PrintWriter pw) {
- pw.println(" Settings:");
-
- pw.print(" ");
- pw.print(KEY_TOP_STATE_SETTLE_TIME);
- pw.print("=");
- TimeUtils.formatDuration(TOP_STATE_SETTLE_TIME, pw);
- pw.println();
- pw.print(" ");
- pw.print(KEY_FG_SERVICE_STATE_SETTLE_TIME);
- pw.print("=");
- TimeUtils.formatDuration(FG_SERVICE_STATE_SETTLE_TIME, pw);
- pw.println();
- pw.print(" ");
- pw.print(KEY_BG_STATE_SETTLE_TIME);
- pw.print("=");
- TimeUtils.formatDuration(BG_STATE_SETTLE_TIME, pw);
- pw.println();
- }
- }
-
- @VisibleForTesting
- final Constants mConstants;
-
- @VisibleForTesting
- final class UidState {
- public final int uid;
-
- public ArrayMap<String, Ops> pkgOps;
-
- // true indicates there is an interested observer, false there isn't but it has such an op
- //TODO: Move foregroundOps and hasForegroundWatchers into the AppOpsServiceInterface.
- public SparseBooleanArray foregroundOps;
- public boolean hasForegroundWatchers;
-
- public UidState(int uid) {
- this.uid = uid;
- }
-
- public void clear() {
- mAppOpsServiceInterface.removeUid(uid);
- if (pkgOps != null) {
- for (String packageName : pkgOps.keySet()) {
- mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
- }
- }
- pkgOps = null;
- }
-
- public boolean isDefault() {
- boolean areAllPackageModesDefault = true;
- if (pkgOps != null) {
- for (String packageName : pkgOps.keySet()) {
- if (!mAppOpsServiceInterface.arePackageModesDefault(packageName,
- UserHandle.getUserId(uid))) {
- areAllPackageModesDefault = false;
- break;
- }
- }
- }
- return (pkgOps == null || pkgOps.isEmpty())
- && mAppOpsServiceInterface.areUidModesDefault(uid)
- && areAllPackageModesDefault;
- }
-
- // Functions for uid mode access and manipulation.
- public SparseIntArray getNonDefaultUidModes() {
- return mAppOpsServiceInterface.getNonDefaultUidModes(uid);
- }
-
- public int getUidMode(int op) {
- return mAppOpsServiceInterface.getUidMode(uid, op);
- }
-
- public boolean setUidMode(int op, int mode) {
- return mAppOpsServiceInterface.setUidMode(uid, op, mode);
- }
-
- @SuppressWarnings("GuardedBy")
- int evalMode(int op, int mode) {
- return getUidStateTracker().evalMode(uid, op, mode);
- }
-
- public void evalForegroundOps() {
- foregroundOps = null;
- foregroundOps = mAppOpsServiceInterface.evalForegroundUidOps(uid, foregroundOps);
- if (pkgOps != null) {
- for (int i = pkgOps.size() - 1; i >= 0; i--) {
- foregroundOps = mAppOpsServiceInterface
- .evalForegroundPackageOps(pkgOps.valueAt(i).packageName,
- foregroundOps,
- UserHandle.getUserId(uid));
- }
- }
- hasForegroundWatchers = false;
- if (foregroundOps != null) {
- for (int i = 0; i < foregroundOps.size(); i++) {
- if (foregroundOps.valueAt(i)) {
- hasForegroundWatchers = true;
- break;
- }
- }
- }
- }
-
- @SuppressWarnings("GuardedBy")
- public int getState() {
- return getUidStateTracker().getUidState(uid);
- }
-
- @SuppressWarnings("GuardedBy")
- public void dump(PrintWriter pw, long nowElapsed) {
- getUidStateTracker().dumpUidState(pw, uid, nowElapsed);
- }
- }
-
- static final class Ops extends SparseArray<Op> {
- final String packageName;
- final UidState uidState;
-
- /**
- * The restriction properties of the package. If {@code null} it could not have been read
- * yet and has to be refreshed.
- */
- @Nullable RestrictionBypass bypass;
-
- /** Lazily populated cache of attributionTags of this package */
- final @NonNull ArraySet<String> knownAttributionTags = new ArraySet<>();
-
- /**
- * Lazily populated cache of <b>valid</b> attributionTags of this package, a set smaller
- * than or equal to {@link #knownAttributionTags}.
- */
- final @NonNull ArraySet<String> validAttributionTags = new ArraySet<>();
-
- Ops(String _packageName, UidState _uidState) {
- packageName = _packageName;
- uidState = _uidState;
- }
- }
-
- /** Returned from {@link #verifyAndGetBypass(int, String, String, String)}. */
- private static final class PackageVerificationResult {
-
- final RestrictionBypass bypass;
- final boolean isAttributionTagValid;
-
- PackageVerificationResult(RestrictionBypass bypass, boolean isAttributionTagValid) {
- this.bypass = bypass;
- this.isAttributionTagValid = isAttributionTagValid;
- }
- }
-
- final class Op {
- int op;
- int uid;
- final UidState uidState;
- final @NonNull String packageName;
-
- /** attributionTag -> AttributedOp */
- final ArrayMap<String, AttributedOp> mAttributions = new ArrayMap<>(1);
-
- Op(UidState uidState, String packageName, int op, int uid) {
- this.op = op;
- this.uid = uid;
- this.uidState = uidState;
- this.packageName = packageName;
- }
-
- @Mode int getMode() {
- return mAppOpsServiceInterface.getPackageMode(packageName, this.op,
- UserHandle.getUserId(this.uid));
- }
-
- void setMode(@Mode int mode) {
- mAppOpsServiceInterface.setPackageMode(packageName, this.op, mode,
- UserHandle.getUserId(this.uid));
- }
-
- void removeAttributionsWithNoTime() {
- for (int i = mAttributions.size() - 1; i >= 0; i--) {
- if (!mAttributions.valueAt(i).hasAnyTime()) {
- mAttributions.removeAt(i);
- }
- }
- }
-
- private @NonNull AttributedOp getOrCreateAttribution(@NonNull Op parent,
- @Nullable String attributionTag) {
- AttributedOp attributedOp;
-
- attributedOp = mAttributions.get(attributionTag);
- if (attributedOp == null) {
- attributedOp = new AttributedOp(AppOpsServiceImpl.this, attributionTag,
- parent);
- mAttributions.put(attributionTag, attributedOp);
- }
-
- return attributedOp;
- }
-
- @NonNull
- OpEntry createEntryLocked() {
- final int numAttributions = mAttributions.size();
-
- final ArrayMap<String, AppOpsManager.AttributedOpEntry> attributionEntries =
- new ArrayMap<>(numAttributions);
- for (int i = 0; i < numAttributions; i++) {
- attributionEntries.put(mAttributions.keyAt(i),
- mAttributions.valueAt(i).createAttributedOpEntryLocked());
- }
-
- return new OpEntry(op, getMode(), attributionEntries);
- }
-
- @NonNull
- OpEntry createSingleAttributionEntryLocked(@Nullable String attributionTag) {
- final int numAttributions = mAttributions.size();
-
- final ArrayMap<String, AttributedOpEntry> attributionEntries = new ArrayMap<>(1);
- for (int i = 0; i < numAttributions; i++) {
- if (Objects.equals(mAttributions.keyAt(i), attributionTag)) {
- attributionEntries.put(mAttributions.keyAt(i),
- mAttributions.valueAt(i).createAttributedOpEntryLocked());
- break;
- }
- }
-
- return new OpEntry(op, getMode(), attributionEntries);
- }
-
- boolean isRunning() {
- final int numAttributions = mAttributions.size();
- for (int i = 0; i < numAttributions; i++) {
- if (mAttributions.valueAt(i).isRunning()) {
- return true;
- }
- }
-
- return false;
- }
- }
-
- final ArrayMap<IBinder, ModeCallback> mModeWatchers = new ArrayMap<>();
- final ArrayMap<IBinder, SparseArray<ActiveCallback>> mActiveWatchers = new ArrayMap<>();
- final ArrayMap<IBinder, SparseArray<StartedCallback>> mStartedWatchers = new ArrayMap<>();
- final ArrayMap<IBinder, SparseArray<NotedCallback>> mNotedWatchers = new ArrayMap<>();
-
- final class ModeCallback extends OnOpModeChangedListener implements DeathRecipient {
- /** If mWatchedOpCode==ALL_OPS notify for ops affected by the switch-op */
- public static final int ALL_OPS = -2;
-
- // Need to keep this only because stopWatchingMode needs an IAppOpsCallback.
- // Otherwise we can just use the IBinder object.
- private final IAppOpsCallback mCallback;
-
- ModeCallback(IAppOpsCallback callback, int watchingUid, int flags, int watchedOpCode,
- int callingUid, int callingPid) {
- super(watchingUid, flags, watchedOpCode, callingUid, callingPid);
- this.mCallback = callback;
- try {
- mCallback.asBinder().linkToDeath(this, 0);
- } catch (RemoteException e) {
- /*ignored*/
- }
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder(128);
- sb.append("ModeCallback{");
- sb.append(Integer.toHexString(System.identityHashCode(this)));
- sb.append(" watchinguid=");
- UserHandle.formatUid(sb, getWatchingUid());
- sb.append(" flags=0x");
- sb.append(Integer.toHexString(getFlags()));
- switch (getWatchedOpCode()) {
- case OP_NONE:
- break;
- case ALL_OPS:
- sb.append(" op=(all)");
- break;
- default:
- sb.append(" op=");
- sb.append(opToName(getWatchedOpCode()));
- break;
- }
- sb.append(" from uid=");
- UserHandle.formatUid(sb, getCallingUid());
- sb.append(" pid=");
- sb.append(getCallingPid());
- sb.append('}');
- return sb.toString();
- }
-
- void unlinkToDeath() {
- mCallback.asBinder().unlinkToDeath(this, 0);
- }
-
- @Override
- public void binderDied() {
- stopWatchingMode(mCallback);
- }
-
- @Override
- public void onOpModeChanged(int op, int uid, String packageName) throws RemoteException {
- mCallback.opChanged(op, uid, packageName);
- }
- }
-
- final class ActiveCallback implements DeathRecipient {
- final IAppOpsActiveCallback mCallback;
- final int mWatchingUid;
- final int mCallingUid;
- final int mCallingPid;
-
- ActiveCallback(IAppOpsActiveCallback callback, int watchingUid, int callingUid,
- int callingPid) {
- mCallback = callback;
- mWatchingUid = watchingUid;
- mCallingUid = callingUid;
- mCallingPid = callingPid;
- try {
- mCallback.asBinder().linkToDeath(this, 0);
- } catch (RemoteException e) {
- /*ignored*/
- }
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder(128);
- sb.append("ActiveCallback{");
- sb.append(Integer.toHexString(System.identityHashCode(this)));
- sb.append(" watchinguid=");
- UserHandle.formatUid(sb, mWatchingUid);
- sb.append(" from uid=");
- UserHandle.formatUid(sb, mCallingUid);
- sb.append(" pid=");
- sb.append(mCallingPid);
- sb.append('}');
- return sb.toString();
- }
-
- void destroy() {
- mCallback.asBinder().unlinkToDeath(this, 0);
- }
-
- @Override
- public void binderDied() {
- stopWatchingActive(mCallback);
- }
- }
-
- final class StartedCallback implements DeathRecipient {
- final IAppOpsStartedCallback mCallback;
- final int mWatchingUid;
- final int mCallingUid;
- final int mCallingPid;
-
- StartedCallback(IAppOpsStartedCallback callback, int watchingUid, int callingUid,
- int callingPid) {
- mCallback = callback;
- mWatchingUid = watchingUid;
- mCallingUid = callingUid;
- mCallingPid = callingPid;
- try {
- mCallback.asBinder().linkToDeath(this, 0);
- } catch (RemoteException e) {
- /*ignored*/
- }
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder(128);
- sb.append("StartedCallback{");
- sb.append(Integer.toHexString(System.identityHashCode(this)));
- sb.append(" watchinguid=");
- UserHandle.formatUid(sb, mWatchingUid);
- sb.append(" from uid=");
- UserHandle.formatUid(sb, mCallingUid);
- sb.append(" pid=");
- sb.append(mCallingPid);
- sb.append('}');
- return sb.toString();
- }
-
- void destroy() {
- mCallback.asBinder().unlinkToDeath(this, 0);
- }
-
- @Override
- public void binderDied() {
- stopWatchingStarted(mCallback);
- }
- }
-
- final class NotedCallback implements DeathRecipient {
- final IAppOpsNotedCallback mCallback;
- final int mWatchingUid;
- final int mCallingUid;
- final int mCallingPid;
-
- NotedCallback(IAppOpsNotedCallback callback, int watchingUid, int callingUid,
- int callingPid) {
- mCallback = callback;
- mWatchingUid = watchingUid;
- mCallingUid = callingUid;
- mCallingPid = callingPid;
- try {
- mCallback.asBinder().linkToDeath(this, 0);
- } catch (RemoteException e) {
- /*ignored*/
- }
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder(128);
- sb.append("NotedCallback{");
- sb.append(Integer.toHexString(System.identityHashCode(this)));
- sb.append(" watchinguid=");
- UserHandle.formatUid(sb, mWatchingUid);
- sb.append(" from uid=");
- UserHandle.formatUid(sb, mCallingUid);
- sb.append(" pid=");
- sb.append(mCallingPid);
- sb.append('}');
- return sb.toString();
- }
-
- void destroy() {
- mCallback.asBinder().unlinkToDeath(this, 0);
- }
-
- @Override
- public void binderDied() {
- stopWatchingNoted(mCallback);
- }
- }
-
- /**
- * Call {@link AttributedOp#onClientDeath attributedOp.onClientDeath(clientId)}.
- */
- static void onClientDeath(@NonNull AttributedOp attributedOp,
- @NonNull IBinder clientId) {
- attributedOp.onClientDeath(clientId);
- }
-
- AppOpsServiceImpl(File storagePath, Handler handler, Context context) {
- mContext = context;
-
- for (int switchedCode = 0; switchedCode < _NUM_OP; switchedCode++) {
- int switchCode = AppOpsManager.opToSwitch(switchedCode);
- mSwitchedOps.put(switchCode,
- ArrayUtils.appendInt(mSwitchedOps.get(switchCode), switchedCode));
- }
- mAppOpsServiceInterface = new AppOpsCheckingServiceTracingDecorator(
- new AppOpsCheckingServiceImpl(this, this, handler, context, mSwitchedOps));
- mAppOpsRestrictions = new AppOpsRestrictionsImpl(context, handler,
- mAppOpsServiceInterface);
-
- LockGuard.installLock(this, LockGuard.INDEX_APP_OPS);
- mFile = new AtomicFile(storagePath, "appops");
-
- mHandler = handler;
- mConstants = new Constants(mHandler);
- readState();
- }
-
- /**
- * Handler for work when packages are removed or updated
- */
- private BroadcastReceiver mOnPackageUpdatedReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- String pkgName = intent.getData().getEncodedSchemeSpecificPart();
- int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID);
-
- if (action.equals(ACTION_PACKAGE_REMOVED) && !intent.hasExtra(EXTRA_REPLACING)) {
- synchronized (AppOpsServiceImpl.this) {
- UidState uidState = mUidStates.get(uid);
- if (uidState == null || uidState.pkgOps == null) {
- return;
- }
- mAppOpsServiceInterface.removePackage(pkgName, UserHandle.getUserId(uid));
- Ops removedOps = uidState.pkgOps.remove(pkgName);
- if (removedOps != null) {
- scheduleFastWriteLocked();
- }
- }
- } else if (action.equals(Intent.ACTION_PACKAGE_REPLACED)) {
- AndroidPackage pkg = getPackageManagerInternal().getPackage(pkgName);
- if (pkg == null) {
- return;
- }
-
- ArrayMap<String, String> dstAttributionTags = new ArrayMap<>();
- ArraySet<String> attributionTags = new ArraySet<>();
- attributionTags.add(null);
- if (pkg.getAttributions() != null) {
- int numAttributions = pkg.getAttributions().size();
- for (int attributionNum = 0; attributionNum < numAttributions;
- attributionNum++) {
- ParsedAttribution attribution = pkg.getAttributions().get(attributionNum);
- attributionTags.add(attribution.getTag());
-
- int numInheritFrom = attribution.getInheritFrom().size();
- for (int inheritFromNum = 0; inheritFromNum < numInheritFrom;
- inheritFromNum++) {
- dstAttributionTags.put(attribution.getInheritFrom().get(inheritFromNum),
- attribution.getTag());
- }
- }
- }
-
- synchronized (AppOpsServiceImpl.this) {
- UidState uidState = mUidStates.get(uid);
- if (uidState == null || uidState.pkgOps == null) {
- return;
- }
-
- Ops ops = uidState.pkgOps.get(pkgName);
- if (ops == null) {
- return;
- }
-
- // Reset cached package properties to re-initialize when needed
- ops.bypass = null;
- ops.knownAttributionTags.clear();
-
- // Merge data collected for removed attributions into their successor
- // attributions
- int numOps = ops.size();
- for (int opNum = 0; opNum < numOps; opNum++) {
- Op op = ops.valueAt(opNum);
-
- int numAttributions = op.mAttributions.size();
- for (int attributionNum = numAttributions - 1; attributionNum >= 0;
- attributionNum--) {
- String attributionTag = op.mAttributions.keyAt(attributionNum);
-
- if (attributionTags.contains(attributionTag)) {
- // attribution still exist after upgrade
- continue;
- }
-
- String newAttributionTag = dstAttributionTags.get(attributionTag);
-
- AttributedOp newAttributedOp = op.getOrCreateAttribution(op,
- newAttributionTag);
- newAttributedOp.add(op.mAttributions.valueAt(attributionNum));
- op.mAttributions.removeAt(attributionNum);
-
- scheduleFastWriteLocked();
- }
- }
- }
- }
- }
- };
-
- @Override
- public void systemReady() {
- synchronized (this) {
- upgradeLocked(mVersionAtBoot);
- }
-
- mConstants.startMonitoring(mContext.getContentResolver());
- mHistoricalRegistry.systemReady(mContext.getContentResolver());
-
- IntentFilter packageUpdateFilter = new IntentFilter();
- packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
- packageUpdateFilter.addDataScheme("package");
-
- mContext.registerReceiverAsUser(mOnPackageUpdatedReceiver, UserHandle.ALL,
- packageUpdateFilter, null, null);
-
- synchronized (this) {
- for (int uidNum = mUidStates.size() - 1; uidNum >= 0; uidNum--) {
- int uid = mUidStates.keyAt(uidNum);
- UidState uidState = mUidStates.valueAt(uidNum);
-
- String[] pkgsInUid = getPackagesForUid(uidState.uid);
- if (ArrayUtils.isEmpty(pkgsInUid)) {
- uidState.clear();
- mUidStates.removeAt(uidNum);
- scheduleFastWriteLocked();
- continue;
- }
-
- ArrayMap<String, Ops> pkgs = uidState.pkgOps;
- if (pkgs == null) {
- continue;
- }
-
- int numPkgs = pkgs.size();
- for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
- String pkg = pkgs.keyAt(pkgNum);
-
- String action;
- if (!ArrayUtils.contains(pkgsInUid, pkg)) {
- action = Intent.ACTION_PACKAGE_REMOVED;
- } else {
- action = Intent.ACTION_PACKAGE_REPLACED;
- }
-
- SystemServerInitThreadPool.submit(
- () -> mOnPackageUpdatedReceiver.onReceive(mContext, new Intent(action)
- .setData(Uri.fromParts("package", pkg, null))
- .putExtra(Intent.EXTRA_UID, uid)),
- "Update app-ops uidState in case package " + pkg + " changed");
- }
- }
- }
-
- final IntentFilter packageSuspendFilter = new IntentFilter();
- packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
- packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
- mContext.registerReceiverAsUser(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final int[] changedUids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
- final String[] changedPkgs = intent.getStringArrayExtra(
- Intent.EXTRA_CHANGED_PACKAGE_LIST);
- for (int code : OPS_RESTRICTED_ON_SUSPEND) {
- ArraySet<OnOpModeChangedListener> onModeChangedListeners;
- synchronized (AppOpsServiceImpl.this) {
- onModeChangedListeners =
- mAppOpsServiceInterface.getOpModeChangedListeners(code);
- if (onModeChangedListeners == null) {
- continue;
- }
- }
- for (int i = 0; i < changedUids.length; i++) {
- final int changedUid = changedUids[i];
- final String changedPkg = changedPkgs[i];
- // We trust packagemanager to insert matching uid and packageNames in the
- // extras
- notifyOpChanged(onModeChangedListeners, code, changedUid, changedPkg);
- }
- }
- }
- }, UserHandle.ALL, packageSuspendFilter, null, null);
- }
-
- @Override
- public void packageRemoved(int uid, String packageName) {
- synchronized (this) {
- UidState uidState = mUidStates.get(uid);
- if (uidState == null) {
- return;
- }
-
- Ops removedOps = null;
-
- // Remove any package state if such.
- if (uidState.pkgOps != null) {
- removedOps = uidState.pkgOps.remove(packageName);
- mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
- }
-
- // If we just nuked the last package state check if the UID is valid.
- if (removedOps != null && uidState.pkgOps.isEmpty()
- && getPackagesForUid(uid).length <= 0) {
- uidState.clear();
- mUidStates.remove(uid);
- }
-
- if (removedOps != null) {
- scheduleFastWriteLocked();
-
- final int numOps = removedOps.size();
- for (int opNum = 0; opNum < numOps; opNum++) {
- final Op op = removedOps.valueAt(opNum);
-
- final int numAttributions = op.mAttributions.size();
- for (int attributionNum = 0; attributionNum < numAttributions;
- attributionNum++) {
- AttributedOp attributedOp = op.mAttributions.valueAt(attributionNum);
-
- while (attributedOp.isRunning()) {
- attributedOp.finished(attributedOp.mInProgressEvents.keyAt(0));
- }
- while (attributedOp.isPaused()) {
- attributedOp.finished(attributedOp.mPausedInProgressEvents.keyAt(0));
- }
- }
- }
- }
- }
-
- mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::clearHistory,
- mHistoricalRegistry, uid, packageName));
- }
-
- @Override
- public void uidRemoved(int uid) {
- synchronized (this) {
- if (mUidStates.indexOfKey(uid) >= 0) {
- mUidStates.get(uid).clear();
- mUidStates.remove(uid);
- scheduleFastWriteLocked();
- }
- }
- }
-
- // The callback method from ForegroundPolicyInterface
- private void onUidStateChanged(int uid, int state, boolean foregroundModeMayChange) {
- synchronized (this) {
- UidState uidState = getUidStateLocked(uid, true);
-
- if (uidState != null && foregroundModeMayChange && uidState.hasForegroundWatchers) {
- for (int fgi = uidState.foregroundOps.size() - 1; fgi >= 0; fgi--) {
- if (!uidState.foregroundOps.valueAt(fgi)) {
- continue;
- }
- final int code = uidState.foregroundOps.keyAt(fgi);
-
- if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)
- && uidState.getUidMode(code) == AppOpsManager.MODE_FOREGROUND) {
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsServiceImpl::notifyOpChangedForAllPkgsInUid,
- this, code, uidState.uid, true, null));
- } else if (uidState.pkgOps != null) {
- final ArraySet<OnOpModeChangedListener> listenerSet =
- mAppOpsServiceInterface.getOpModeChangedListeners(code);
- if (listenerSet != null) {
- for (int cbi = listenerSet.size() - 1; cbi >= 0; cbi--) {
- final OnOpModeChangedListener listener = listenerSet.valueAt(cbi);
- if ((listener.getFlags()
- & AppOpsManager.WATCH_FOREGROUND_CHANGES) == 0
- || !listener.isWatchingUid(uidState.uid)) {
- continue;
- }
- for (int pkgi = uidState.pkgOps.size() - 1; pkgi >= 0; pkgi--) {
- final Op op = uidState.pkgOps.valueAt(pkgi).get(code);
- if (op == null) {
- continue;
- }
- if (op.getMode() == AppOpsManager.MODE_FOREGROUND) {
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsServiceImpl::notifyOpChanged,
- this, listenerSet.valueAt(cbi), code, uidState.uid,
- uidState.pkgOps.keyAt(pkgi)));
- }
- }
- }
- }
- }
- }
- }
-
- if (uidState != null && uidState.pkgOps != null) {
- int numPkgs = uidState.pkgOps.size();
- for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
- Ops ops = uidState.pkgOps.valueAt(pkgNum);
-
- int numOps = ops.size();
- for (int opNum = 0; opNum < numOps; opNum++) {
- Op op = ops.valueAt(opNum);
-
- int numAttributions = op.mAttributions.size();
- for (int attributionNum = 0; attributionNum < numAttributions;
- attributionNum++) {
- AttributedOp attributedOp = op.mAttributions.valueAt(
- attributionNum);
-
- attributedOp.onUidStateChanged(state);
- }
- }
- }
- }
- }
- }
-
- /**
- * Notify the proc state or capability has changed for a certain UID.
- */
- @Override
- public void updateUidProcState(int uid, int procState,
- @ActivityManager.ProcessCapability int capability) {
- synchronized (this) {
- getUidStateTracker().updateUidProcState(uid, procState, capability);
- if (!mUidStates.contains(uid)) {
- UidState uidState = new UidState(uid);
- mUidStates.put(uid, uidState);
- onUidStateChanged(uid,
- AppOpsUidStateTracker.processStateToUidState(procState), false);
- }
- }
- }
-
- @Override
- public void shutdown() {
- Slog.w(TAG, "Writing app ops before shutdown...");
- boolean doWrite = false;
- synchronized (this) {
- if (mWriteScheduled) {
- mWriteScheduled = false;
- mFastWriteScheduled = false;
- mHandler.removeCallbacks(mWriteRunner);
- doWrite = true;
- }
- }
- if (doWrite) {
- writeState();
- }
-
- mHistoricalRegistry.shutdown();
- }
-
- private ArrayList<AppOpsManager.OpEntry> collectOps(Ops pkgOps, int[] ops) {
- ArrayList<AppOpsManager.OpEntry> resOps = null;
- if (ops == null) {
- resOps = new ArrayList<>();
- for (int j = 0; j < pkgOps.size(); j++) {
- Op curOp = pkgOps.valueAt(j);
- resOps.add(getOpEntryForResult(curOp));
- }
- } else {
- for (int j = 0; j < ops.length; j++) {
- Op curOp = pkgOps.get(ops[j]);
- if (curOp != null) {
- if (resOps == null) {
- resOps = new ArrayList<>();
- }
- resOps.add(getOpEntryForResult(curOp));
- }
- }
- }
- return resOps;
- }
-
- @Nullable
- private ArrayList<AppOpsManager.OpEntry> collectUidOps(@NonNull UidState uidState,
- @Nullable int[] ops) {
- final SparseIntArray opModes = uidState.getNonDefaultUidModes();
- if (opModes == null) {
- return null;
- }
-
- int opModeCount = opModes.size();
- if (opModeCount == 0) {
- return null;
- }
- ArrayList<AppOpsManager.OpEntry> resOps = null;
- if (ops == null) {
- resOps = new ArrayList<>();
- for (int i = 0; i < opModeCount; i++) {
- int code = opModes.keyAt(i);
- resOps.add(new OpEntry(code, opModes.get(code), Collections.emptyMap()));
- }
- } else {
- for (int j = 0; j < ops.length; j++) {
- int code = ops[j];
- if (opModes.indexOfKey(code) >= 0) {
- if (resOps == null) {
- resOps = new ArrayList<>();
- }
- resOps.add(new OpEntry(code, opModes.get(code), Collections.emptyMap()));
- }
- }
- }
- return resOps;
- }
-
- private static @NonNull OpEntry getOpEntryForResult(@NonNull Op op) {
- return op.createEntryLocked();
- }
-
- @Override
- public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) {
- final int callingUid = Binder.getCallingUid();
- final boolean hasAllPackageAccess = mContext.checkPermission(
- Manifest.permission.GET_APP_OPS_STATS, Binder.getCallingPid(),
- Binder.getCallingUid(), null) == PackageManager.PERMISSION_GRANTED;
- ArrayList<AppOpsManager.PackageOps> res = null;
- synchronized (this) {
- final int uidStateCount = mUidStates.size();
- for (int i = 0; i < uidStateCount; i++) {
- UidState uidState = mUidStates.valueAt(i);
- if (uidState.pkgOps == null || uidState.pkgOps.isEmpty()) {
- continue;
- }
- ArrayMap<String, Ops> packages = uidState.pkgOps;
- final int packageCount = packages.size();
- for (int j = 0; j < packageCount; j++) {
- Ops pkgOps = packages.valueAt(j);
- ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
- if (resOps != null) {
- if (res == null) {
- res = new ArrayList<>();
- }
- AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
- pkgOps.packageName, pkgOps.uidState.uid, resOps);
- // Caller can always see their packages and with a permission all.
- if (hasAllPackageAccess || callingUid == pkgOps.uidState.uid) {
- res.add(resPackage);
- }
- }
- }
- }
- }
- return res;
- }
-
- @Override
- public List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName,
- int[] ops) {
- enforceGetAppOpsStatsPermissionIfNeeded(uid, packageName);
- String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
- if (resolvedPackageName == null) {
- return Collections.emptyList();
- }
- synchronized (this) {
- Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, false, null,
- /* edit */ false);
- if (pkgOps == null) {
- return null;
- }
- ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
- if (resOps == null) {
- return null;
- }
- ArrayList<AppOpsManager.PackageOps> res = new ArrayList<>();
- AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
- pkgOps.packageName, pkgOps.uidState.uid, resOps);
- res.add(resPackage);
- return res;
- }
- }
-
- private void enforceGetAppOpsStatsPermissionIfNeeded(int uid, String packageName) {
- final int callingUid = Binder.getCallingUid();
- // We get to access everything
- if (callingUid == Process.myPid()) {
- return;
- }
- // Apps can access their own data
- if (uid == callingUid && packageName != null
- && checkPackage(uid, packageName) == MODE_ALLOWED) {
- return;
- }
- // Otherwise, you need a permission...
- mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
- Binder.getCallingPid(), callingUid, null);
- }
-
- /**
- * Verify that historical appop request arguments are valid.
- */
- private void ensureHistoricalOpRequestIsValid(int uid, String packageName,
- String attributionTag, List<String> opNames, int filter, long beginTimeMillis,
- long endTimeMillis, int flags) {
- if ((filter & FILTER_BY_UID) != 0) {
- Preconditions.checkArgument(uid != Process.INVALID_UID);
- } else {
- Preconditions.checkArgument(uid == Process.INVALID_UID);
- }
-
- if ((filter & FILTER_BY_PACKAGE_NAME) != 0) {
- Objects.requireNonNull(packageName);
- } else {
- Preconditions.checkArgument(packageName == null);
- }
-
- if ((filter & FILTER_BY_ATTRIBUTION_TAG) == 0) {
- Preconditions.checkArgument(attributionTag == null);
- }
-
- if ((filter & FILTER_BY_OP_NAMES) != 0) {
- Objects.requireNonNull(opNames);
- } else {
- Preconditions.checkArgument(opNames == null);
- }
-
- Preconditions.checkFlagsArgument(filter,
- FILTER_BY_UID | FILTER_BY_PACKAGE_NAME | FILTER_BY_ATTRIBUTION_TAG
- | FILTER_BY_OP_NAMES);
- Preconditions.checkArgumentNonnegative(beginTimeMillis);
- Preconditions.checkArgument(endTimeMillis > beginTimeMillis);
- Preconditions.checkFlagsArgument(flags, OP_FLAGS_ALL);
- }
-
- @Override
- public void getHistoricalOps(int uid, String packageName, String attributionTag,
- List<String> opNames, int dataType, int filter, long beginTimeMillis,
- long endTimeMillis, int flags, RemoteCallback callback) {
- PackageManager pm = mContext.getPackageManager();
-
- ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
- beginTimeMillis, endTimeMillis, flags);
- Objects.requireNonNull(callback, "callback cannot be null");
- ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
- boolean isSelfRequest = (filter & FILTER_BY_UID) != 0 && uid == Binder.getCallingUid();
- if (!isSelfRequest) {
- boolean isCallerInstrumented =
- ami.getInstrumentationSourceUid(Binder.getCallingUid()) != Process.INVALID_UID;
- boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
- boolean isCallerPermissionController;
- try {
- isCallerPermissionController = pm.getPackageUidAsUser(
- mContext.getPackageManager().getPermissionControllerPackageName(), 0,
- UserHandle.getUserId(Binder.getCallingUid()))
- == Binder.getCallingUid();
- } catch (PackageManager.NameNotFoundException doesNotHappen) {
- return;
- }
-
- boolean doesCallerHavePermission = mContext.checkPermission(
- android.Manifest.permission.GET_HISTORICAL_APP_OPS_STATS,
- Binder.getCallingPid(), Binder.getCallingUid())
- == PackageManager.PERMISSION_GRANTED;
-
- if (!isCallerSystem && !isCallerInstrumented && !isCallerPermissionController
- && !doesCallerHavePermission) {
- mHandler.post(() -> callback.sendResult(new Bundle()));
- return;
- }
-
- mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
- Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
- }
-
- final String[] opNamesArray = (opNames != null)
- ? opNames.toArray(new String[opNames.size()]) : null;
-
- Set<String> attributionChainExemptPackages = null;
- if ((dataType & HISTORY_FLAG_GET_ATTRIBUTION_CHAINS) != 0) {
- attributionChainExemptPackages =
- PermissionManager.getIndicatorExemptedPackages(mContext);
- }
-
- final String[] chainExemptPkgArray = attributionChainExemptPackages != null
- ? attributionChainExemptPackages.toArray(
- new String[attributionChainExemptPackages.size()]) : null;
-
- // Must not hold the appops lock
- mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOps,
- mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType,
- filter, beginTimeMillis, endTimeMillis, flags, chainExemptPkgArray,
- callback).recycleOnUse());
- }
-
- @Override
- public void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag,
- List<String> opNames, int dataType, int filter, long beginTimeMillis,
- long endTimeMillis, int flags, RemoteCallback callback) {
- ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
- beginTimeMillis, endTimeMillis, flags);
- Objects.requireNonNull(callback, "callback cannot be null");
-
- mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
- Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
-
- final String[] opNamesArray = (opNames != null)
- ? opNames.toArray(new String[opNames.size()]) : null;
-
- Set<String> attributionChainExemptPackages = null;
- if ((dataType & HISTORY_FLAG_GET_ATTRIBUTION_CHAINS) != 0) {
- attributionChainExemptPackages =
- PermissionManager.getIndicatorExemptedPackages(mContext);
- }
-
- final String[] chainExemptPkgArray = attributionChainExemptPackages != null
- ? attributionChainExemptPackages.toArray(
- new String[attributionChainExemptPackages.size()]) : null;
-
- // Must not hold the appops lock
- mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOpsFromDiskRaw,
- mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType,
- filter, beginTimeMillis, endTimeMillis, flags, chainExemptPkgArray,
- callback).recycleOnUse());
- }
-
- @Override
- public void reloadNonHistoricalState() {
- mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
- Binder.getCallingPid(), Binder.getCallingUid(), "reloadNonHistoricalState");
- writeState();
- readState();
- }
-
- @Override
- public List<AppOpsManager.PackageOps> getUidOps(int uid, int[] ops) {
- mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
- Binder.getCallingPid(), Binder.getCallingUid(), null);
- synchronized (this) {
- UidState uidState = getUidStateLocked(uid, false);
- if (uidState == null) {
- return null;
- }
- ArrayList<AppOpsManager.OpEntry> resOps = collectUidOps(uidState, ops);
- if (resOps == null) {
- return null;
- }
- ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>();
- AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
- null, uidState.uid, resOps);
- res.add(resPackage);
- return res;
- }
- }
-
- private void pruneOpLocked(Op op, int uid, String packageName) {
- op.removeAttributionsWithNoTime();
-
- if (op.mAttributions.isEmpty()) {
- Ops ops = getOpsLocked(uid, packageName, null, false, null, /* edit */ false);
- if (ops != null) {
- ops.remove(op.op);
- op.setMode(AppOpsManager.opToDefaultMode(op.op));
- if (ops.size() <= 0) {
- UidState uidState = ops.uidState;
- ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
- if (pkgOps != null) {
- pkgOps.remove(ops.packageName);
- mAppOpsServiceInterface.removePackage(ops.packageName,
- UserHandle.getUserId(uidState.uid));
- if (pkgOps.isEmpty()) {
- uidState.pkgOps = null;
- }
- if (uidState.isDefault()) {
- uidState.clear();
- mUidStates.remove(uid);
- }
- }
- }
- }
- }
- }
-
- @Override
- public void enforceManageAppOpsModes(int callingPid, int callingUid, int targetUid) {
- if (callingPid == Process.myPid()) {
- return;
- }
- final int callingUser = UserHandle.getUserId(callingUid);
- synchronized (this) {
- if (mProfileOwners != null && mProfileOwners.get(callingUser, -1) == callingUid) {
- if (targetUid >= 0 && callingUser == UserHandle.getUserId(targetUid)) {
- // Profile owners are allowed to change modes but only for apps
- // within their user.
- return;
- }
- }
- }
- mContext.enforcePermission(android.Manifest.permission.MANAGE_APP_OPS_MODES,
- Binder.getCallingPid(), Binder.getCallingUid(), null);
- }
-
- @Override
- public void setUidMode(int code, int uid, int mode,
- @Nullable IAppOpsCallback permissionPolicyCallback) {
- if (DEBUG) {
- Slog.i(TAG, "uid " + uid + " OP_" + opToName(code) + " := " + modeToName(mode)
- + " by uid " + Binder.getCallingUid());
- }
-
- enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
- verifyIncomingOp(code);
- code = AppOpsManager.opToSwitch(code);
-
- if (permissionPolicyCallback == null) {
- updatePermissionRevokedCompat(uid, code, mode);
- }
-
- int previousMode;
- synchronized (this) {
- final int defaultMode = AppOpsManager.opToDefaultMode(code);
-
- UidState uidState = getUidStateLocked(uid, false);
- if (uidState == null) {
- if (mode == defaultMode) {
- return;
- }
- uidState = new UidState(uid);
- mUidStates.put(uid, uidState);
- }
- if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)) {
- previousMode = uidState.getUidMode(code);
- } else {
- // doesn't look right but is legacy behavior.
- previousMode = MODE_DEFAULT;
- }
-
- if (!uidState.setUidMode(code, mode)) {
- return;
- }
- uidState.evalForegroundOps();
- if (mode != MODE_ERRORED && mode != previousMode) {
- updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
- }
- }
-
- notifyOpChangedForAllPkgsInUid(code, uid, false, permissionPolicyCallback);
- notifyOpChangedSync(code, uid, null, mode, previousMode);
- }
-
- /**
- * Notify that an op changed for all packages in an uid.
- *
- * @param code The op that changed
- * @param uid The uid the op was changed for
- * @param onlyForeground Only notify watchers that watch for foreground changes
- */
- private void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground,
- @Nullable IAppOpsCallback callbackToIgnore) {
- ModeCallback listenerToIgnore = callbackToIgnore != null
- ? mModeWatchers.get(callbackToIgnore.asBinder()) : null;
- mAppOpsServiceInterface.notifyOpChangedForAllPkgsInUid(code, uid, onlyForeground,
- listenerToIgnore);
- }
-
- private void updatePermissionRevokedCompat(int uid, int switchCode, int mode) {
- PackageManager packageManager = mContext.getPackageManager();
- if (packageManager == null) {
- // This can only happen during early boot. At this time the permission state and appop
- // state are in sync
- return;
- }
-
- String[] packageNames = packageManager.getPackagesForUid(uid);
- if (ArrayUtils.isEmpty(packageNames)) {
- return;
- }
- String packageName = packageNames[0];
-
- int[] ops = mSwitchedOps.get(switchCode);
- for (int code : ops) {
- String permissionName = AppOpsManager.opToPermission(code);
- if (permissionName == null) {
- continue;
- }
-
- if (packageManager.checkPermission(permissionName, packageName)
- != PackageManager.PERMISSION_GRANTED) {
- continue;
- }
-
- PermissionInfo permissionInfo;
- try {
- permissionInfo = packageManager.getPermissionInfo(permissionName, 0);
- } catch (PackageManager.NameNotFoundException e) {
- e.printStackTrace();
- continue;
- }
-
- if (!permissionInfo.isRuntime()) {
- continue;
- }
-
- boolean supportsRuntimePermissions = getPackageManagerInternal()
- .getUidTargetSdkVersion(uid) >= Build.VERSION_CODES.M;
-
- UserHandle user = UserHandle.getUserHandleForUid(uid);
- boolean isRevokedCompat;
- if (permissionInfo.backgroundPermission != null) {
- if (packageManager.checkPermission(permissionInfo.backgroundPermission, packageName)
- == PackageManager.PERMISSION_GRANTED) {
- boolean isBackgroundRevokedCompat = mode != AppOpsManager.MODE_ALLOWED;
-
- if (isBackgroundRevokedCompat && supportsRuntimePermissions) {
- Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime"
- + " permission state, this is discouraged and you should revoke the"
- + " runtime permission instead: uid=" + uid + ", switchCode="
- + switchCode + ", mode=" + mode + ", permission="
- + permissionInfo.backgroundPermission);
- }
-
- final long identity = Binder.clearCallingIdentity();
- try {
- packageManager.updatePermissionFlags(permissionInfo.backgroundPermission,
- packageName, PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
- isBackgroundRevokedCompat
- ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED
- && mode != AppOpsManager.MODE_FOREGROUND;
- } else {
- isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED;
- }
-
- if (isRevokedCompat && supportsRuntimePermissions) {
- Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime"
- + " permission state, this is discouraged and you should revoke the"
- + " runtime permission instead: uid=" + uid + ", switchCode="
- + switchCode + ", mode=" + mode + ", permission=" + permissionName);
- }
-
- final long identity = Binder.clearCallingIdentity();
- try {
- packageManager.updatePermissionFlags(permissionName, packageName,
- PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, isRevokedCompat
- ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- }
-
- private void notifyOpChangedSync(int code, int uid, @NonNull String packageName, int mode,
- int previousMode) {
- final StorageManagerInternal storageManagerInternal =
- LocalServices.getService(StorageManagerInternal.class);
- if (storageManagerInternal != null) {
- storageManagerInternal.onAppOpsChanged(code, uid, packageName, mode, previousMode);
- }
- }
-
- @Override
- public void setMode(int code, int uid, @NonNull String packageName, int mode,
- @Nullable IAppOpsCallback permissionPolicyCallback) {
- enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
- verifyIncomingOp(code);
- if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
- return;
- }
-
- ArraySet<OnOpModeChangedListener> repCbs = null;
- code = AppOpsManager.opToSwitch(code);
-
- PackageVerificationResult pvr;
- try {
- pvr = verifyAndGetBypass(uid, packageName, null);
- } catch (SecurityException e) {
- Slog.e(TAG, "Cannot setMode", e);
- return;
- }
-
- int previousMode = MODE_DEFAULT;
- synchronized (this) {
- UidState uidState = getUidStateLocked(uid, false);
- Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ true);
- if (op != null) {
- if (op.getMode() != mode) {
- previousMode = op.getMode();
- op.setMode(mode);
-
- if (uidState != null) {
- uidState.evalForegroundOps();
- }
- ArraySet<OnOpModeChangedListener> cbs =
- mAppOpsServiceInterface.getOpModeChangedListeners(code);
- if (cbs != null) {
- if (repCbs == null) {
- repCbs = new ArraySet<>();
- }
- repCbs.addAll(cbs);
- }
- cbs = mAppOpsServiceInterface.getPackageModeChangedListeners(packageName);
- if (cbs != null) {
- if (repCbs == null) {
- repCbs = new ArraySet<>();
- }
- repCbs.addAll(cbs);
- }
- if (repCbs != null && permissionPolicyCallback != null) {
- repCbs.remove(mModeWatchers.get(permissionPolicyCallback.asBinder()));
- }
- if (mode == AppOpsManager.opToDefaultMode(op.op)) {
- // If going into the default mode, prune this op
- // if there is nothing else interesting in it.
- pruneOpLocked(op, uid, packageName);
- }
- scheduleFastWriteLocked();
- if (mode != MODE_ERRORED) {
- updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
- }
- }
- }
- }
- if (repCbs != null) {
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsServiceImpl::notifyOpChanged,
- this, repCbs, code, uid, packageName));
- }
-
- notifyOpChangedSync(code, uid, packageName, mode, previousMode);
- }
-
- private void notifyOpChanged(ArraySet<OnOpModeChangedListener> callbacks, int code,
- int uid, String packageName) {
- for (int i = 0; i < callbacks.size(); i++) {
- final OnOpModeChangedListener callback = callbacks.valueAt(i);
- notifyOpChanged(callback, code, uid, packageName);
- }
- }
-
- private void notifyOpChanged(OnOpModeChangedListener callback, int code,
- int uid, String packageName) {
- mAppOpsServiceInterface.notifyOpChanged(callback, code, uid, packageName);
- }
-
- private static ArrayList<ChangeRec> addChange(ArrayList<ChangeRec> reports,
- int op, int uid, String packageName, int previousMode) {
- boolean duplicate = false;
- if (reports == null) {
- reports = new ArrayList<>();
- } else {
- final int reportCount = reports.size();
- for (int j = 0; j < reportCount; j++) {
- ChangeRec report = reports.get(j);
- if (report.op == op && report.pkg.equals(packageName)) {
- duplicate = true;
- break;
- }
- }
- }
- if (!duplicate) {
- reports.add(new ChangeRec(op, uid, packageName, previousMode));
- }
-
- return reports;
- }
-
- private static HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> addCallbacks(
- HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks,
- int op, int uid, String packageName, int previousMode,
- ArraySet<OnOpModeChangedListener> cbs) {
- if (cbs == null) {
- return callbacks;
- }
- if (callbacks == null) {
- callbacks = new HashMap<>();
- }
- final int N = cbs.size();
- for (int i=0; i<N; i++) {
- OnOpModeChangedListener cb = cbs.valueAt(i);
- ArrayList<ChangeRec> reports = callbacks.get(cb);
- ArrayList<ChangeRec> changed = addChange(reports, op, uid, packageName, previousMode);
- if (changed != reports) {
- callbacks.put(cb, changed);
- }
- }
- return callbacks;
- }
-
- static final class ChangeRec {
- final int op;
- final int uid;
- final String pkg;
- final int previous_mode;
-
- ChangeRec(int _op, int _uid, String _pkg, int _previous_mode) {
- op = _op;
- uid = _uid;
- pkg = _pkg;
- previous_mode = _previous_mode;
- }
- }
-
- @Override
- public void resetAllModes(int reqUserId, String reqPackageName) {
- final int callingPid = Binder.getCallingPid();
- final int callingUid = Binder.getCallingUid();
- reqUserId = ActivityManager.handleIncomingUser(callingPid, callingUid, reqUserId,
- true, true, "resetAllModes", null);
-
- int reqUid = -1;
- if (reqPackageName != null) {
- try {
- reqUid = AppGlobals.getPackageManager().getPackageUid(
- reqPackageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, reqUserId);
- } catch (RemoteException e) {
- /* ignore - local call */
- }
- }
-
- enforceManageAppOpsModes(callingPid, callingUid, reqUid);
-
- HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks = null;
- ArrayList<ChangeRec> allChanges = new ArrayList<>();
- synchronized (this) {
- boolean changed = false;
- for (int i = mUidStates.size() - 1; i >= 0; i--) {
- UidState uidState = mUidStates.valueAt(i);
-
- SparseIntArray opModes = uidState.getNonDefaultUidModes();
- if (opModes != null && (uidState.uid == reqUid || reqUid == -1)) {
- final int uidOpCount = opModes.size();
- for (int j = uidOpCount - 1; j >= 0; j--) {
- final int code = opModes.keyAt(j);
- if (AppOpsManager.opAllowsReset(code)) {
- int previousMode = opModes.valueAt(j);
- uidState.setUidMode(code, AppOpsManager.opToDefaultMode(code));
- for (String packageName : getPackagesForUid(uidState.uid)) {
- callbacks = addCallbacks(callbacks, code, uidState.uid,
- packageName, previousMode,
- mAppOpsServiceInterface.getOpModeChangedListeners(code));
- callbacks = addCallbacks(callbacks, code, uidState.uid,
- packageName, previousMode, mAppOpsServiceInterface
- .getPackageModeChangedListeners(packageName));
-
- allChanges = addChange(allChanges, code, uidState.uid,
- packageName, previousMode);
- }
- }
- }
- }
-
- if (uidState.pkgOps == null) {
- continue;
- }
-
- if (reqUserId != UserHandle.USER_ALL
- && reqUserId != UserHandle.getUserId(uidState.uid)) {
- // Skip any ops for a different user
- continue;
- }
-
- Map<String, Ops> packages = uidState.pkgOps;
- Iterator<Map.Entry<String, Ops>> it = packages.entrySet().iterator();
- boolean uidChanged = false;
- while (it.hasNext()) {
- Map.Entry<String, Ops> ent = it.next();
- String packageName = ent.getKey();
- if (reqPackageName != null && !reqPackageName.equals(packageName)) {
- // Skip any ops for a different package
- continue;
- }
- Ops pkgOps = ent.getValue();
- for (int j=pkgOps.size()-1; j>=0; j--) {
- Op curOp = pkgOps.valueAt(j);
- if (shouldDeferResetOpToDpm(curOp.op)) {
- deferResetOpToDpm(curOp.op, reqPackageName, reqUserId);
- continue;
- }
- if (AppOpsManager.opAllowsReset(curOp.op)
- && curOp.getMode() != AppOpsManager.opToDefaultMode(curOp.op)) {
- int previousMode = curOp.getMode();
- curOp.setMode(AppOpsManager.opToDefaultMode(curOp.op));
- changed = true;
- uidChanged = true;
- final int uid = curOp.uidState.uid;
- callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
- previousMode,
- mAppOpsServiceInterface.getOpModeChangedListeners(curOp.op));
- callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
- previousMode, mAppOpsServiceInterface
- .getPackageModeChangedListeners(packageName));
-
- allChanges = addChange(allChanges, curOp.op, uid, packageName,
- previousMode);
- curOp.removeAttributionsWithNoTime();
- if (curOp.mAttributions.isEmpty()) {
- pkgOps.removeAt(j);
- }
- }
- }
- if (pkgOps.size() == 0) {
- it.remove();
- mAppOpsServiceInterface.removePackage(packageName,
- UserHandle.getUserId(uidState.uid));
- }
- }
- if (uidState.isDefault()) {
- uidState.clear();
- mUidStates.remove(uidState.uid);
- }
- if (uidChanged) {
- uidState.evalForegroundOps();
- }
- }
-
- if (changed) {
- scheduleFastWriteLocked();
- }
- }
- if (callbacks != null) {
- for (Map.Entry<OnOpModeChangedListener, ArrayList<ChangeRec>> ent
- : callbacks.entrySet()) {
- OnOpModeChangedListener cb = ent.getKey();
- ArrayList<ChangeRec> reports = ent.getValue();
- for (int i=0; i<reports.size(); i++) {
- ChangeRec rep = reports.get(i);
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsServiceImpl::notifyOpChanged,
- this, cb, rep.op, rep.uid, rep.pkg));
- }
- }
- }
-
- int numChanges = allChanges.size();
- for (int i = 0; i < numChanges; i++) {
- ChangeRec change = allChanges.get(i);
- notifyOpChangedSync(change.op, change.uid, change.pkg,
- AppOpsManager.opToDefaultMode(change.op), change.previous_mode);
- }
- }
-
- private boolean shouldDeferResetOpToDpm(int op) {
- // TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission
- // pre-grants to a role-based mechanism or another general-purpose mechanism.
- return dpmi != null && dpmi.supportsResetOp(op);
- }
-
- /** Assumes {@link #shouldDeferResetOpToDpm(int)} is true. */
- private void deferResetOpToDpm(int op, String packageName, @UserIdInt int userId) {
- // TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission
- // pre-grants to a role-based mechanism or another general-purpose mechanism.
- dpmi.resetOp(op, packageName, userId);
- }
-
- private void evalAllForegroundOpsLocked() {
- for (int uidi = mUidStates.size() - 1; uidi >= 0; uidi--) {
- final UidState uidState = mUidStates.valueAt(uidi);
- if (uidState.foregroundOps != null) {
- uidState.evalForegroundOps();
- }
- }
- }
-
- @Override
- public void startWatchingModeWithFlags(int op, String packageName, int flags,
- IAppOpsCallback callback) {
- int watchedUid = -1;
- final int callingUid = Binder.getCallingUid();
- final int callingPid = Binder.getCallingPid();
- // TODO: should have a privileged permission to protect this.
- // Also, if the caller has requested WATCH_FOREGROUND_CHANGES, should we require
- // the USAGE_STATS permission since this can provide information about when an
- // app is in the foreground?
- Preconditions.checkArgumentInRange(op, AppOpsManager.OP_NONE,
- AppOpsManager._NUM_OP - 1, "Invalid op code: " + op);
- if (callback == null) {
- return;
- }
- final boolean mayWatchPackageName = packageName != null
- && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(callingUid));
- synchronized (this) {
- int switchOp = (op != AppOpsManager.OP_NONE) ? AppOpsManager.opToSwitch(op) : op;
-
- int notifiedOps;
- if ((flags & CALL_BACK_ON_SWITCHED_OP) == 0) {
- if (op == OP_NONE) {
- notifiedOps = ALL_OPS;
- } else {
- notifiedOps = op;
- }
- } else {
- notifiedOps = switchOp;
- }
-
- ModeCallback cb = mModeWatchers.get(callback.asBinder());
- if (cb == null) {
- cb = new ModeCallback(callback, watchedUid, flags, notifiedOps, callingUid,
- callingPid);
- mModeWatchers.put(callback.asBinder(), cb);
- }
- if (switchOp != AppOpsManager.OP_NONE) {
- mAppOpsServiceInterface.startWatchingOpModeChanged(cb, switchOp);
- }
- if (mayWatchPackageName) {
- mAppOpsServiceInterface.startWatchingPackageModeChanged(cb, packageName);
- }
- evalAllForegroundOpsLocked();
- }
- }
-
- @Override
- public void stopWatchingMode(IAppOpsCallback callback) {
- if (callback == null) {
- return;
- }
- synchronized (this) {
- ModeCallback cb = mModeWatchers.remove(callback.asBinder());
- if (cb != null) {
- cb.unlinkToDeath();
- mAppOpsServiceInterface.removeListener(cb);
- }
-
- evalAllForegroundOpsLocked();
- }
- }
-
- @Override
- public int checkOperation(int code, int uid, String packageName,
- @Nullable String attributionTag, boolean raw) {
- verifyIncomingOp(code);
- if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
- return AppOpsManager.opToDefaultMode(code);
- }
-
- String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
- if (resolvedPackageName == null) {
- return AppOpsManager.MODE_IGNORED;
- }
- return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag, raw);
- }
-
- /**
- * Get the mode of an app-op.
- *
- * @param code The code of the op
- * @param uid The uid of the package the op belongs to
- * @param packageName The package the op belongs to
- * @param raw If the raw state of eval-ed state should be checked.
- * @return The mode of the op
- */
- private @Mode int checkOperationUnchecked(int code, int uid, @NonNull String packageName,
- @Nullable String attributionTag, boolean raw) {
- PackageVerificationResult pvr;
- try {
- pvr = verifyAndGetBypass(uid, packageName, null);
- } catch (SecurityException e) {
- Slog.e(TAG, "checkOperation", e);
- return AppOpsManager.opToDefaultMode(code);
- }
-
- if (isOpRestrictedDueToSuspend(code, packageName, uid)) {
- return AppOpsManager.MODE_IGNORED;
- }
- synchronized (this) {
- if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, true)) {
- return AppOpsManager.MODE_IGNORED;
- }
- code = AppOpsManager.opToSwitch(code);
- UidState uidState = getUidStateLocked(uid, false);
- if (uidState != null
- && uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)) {
- final int rawMode = uidState.getUidMode(code);
- return raw ? rawMode : uidState.evalMode(code, rawMode);
- }
- Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ false);
- if (op == null) {
- return AppOpsManager.opToDefaultMode(code);
- }
- return raw ? op.getMode() : op.uidState.evalMode(op.op, op.getMode());
- }
- }
-
- @Override
- public int checkPackage(int uid, String packageName) {
- Objects.requireNonNull(packageName);
- try {
- verifyAndGetBypass(uid, packageName, null);
- // When the caller is the system, it's possible that the packageName is the special
- // one (e.g., "root") which isn't actually existed.
- if (resolveUid(packageName) == uid
- || (isPackageExisted(packageName)
- && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(uid)))) {
- return AppOpsManager.MODE_ALLOWED;
- }
- return AppOpsManager.MODE_ERRORED;
- } catch (SecurityException ignored) {
- return AppOpsManager.MODE_ERRORED;
- }
- }
-
- private boolean isPackageExisted(String packageName) {
- return getPackageManagerInternal().getPackageStateInternal(packageName) != null;
- }
-
- /**
- * This method will check with PackageManager to determine if the package provided should
- * be visible to the {@link Binder#getCallingUid()}.
- *
- * NOTE: This must not be called while synchronized on {@code this} to avoid dead locks
- */
- private boolean filterAppAccessUnlocked(String packageName, int userId) {
- final int callingUid = Binder.getCallingUid();
- return LocalServices.getService(PackageManagerInternal.class)
- .filterAppAccess(packageName, callingUid, userId);
- }
-
- @Override
- public int noteOperation(int code, int uid, @Nullable String packageName,
- @Nullable String attributionTag, @Nullable String message) {
- verifyIncomingUid(uid);
- verifyIncomingOp(code);
- if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
- return AppOpsManager.MODE_ERRORED;
- }
-
- String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
- if (resolvedPackageName == null) {
- return AppOpsManager.MODE_IGNORED;
- }
- return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
- Process.INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
- }
-
- @Override
- public int noteOperationUnchecked(int code, int uid, @NonNull String packageName,
- @Nullable String attributionTag, int proxyUid, String proxyPackageName,
- @Nullable String proxyAttributionTag, @OpFlags int flags) {
- PackageVerificationResult pvr;
- try {
- pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
- if (!pvr.isAttributionTagValid) {
- attributionTag = null;
- }
- } catch (SecurityException e) {
- Slog.e(TAG, "noteOperation", e);
- return AppOpsManager.MODE_ERRORED;
- }
-
- synchronized (this) {
- final Ops ops = getOpsLocked(uid, packageName, attributionTag,
- pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
- if (ops == null) {
- scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- AppOpsManager.MODE_IGNORED);
- if (DEBUG) {
- Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
- + " package " + packageName + "flags: "
- + AppOpsManager.flagsToString(flags));
- }
- return AppOpsManager.MODE_ERRORED;
- }
- final Op op = getOpLocked(ops, code, uid, true);
- final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
- if (attributedOp.isRunning()) {
- Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName + " code "
- + code + " startTime of in progress event="
- + attributedOp.mInProgressEvents.valueAt(0).getStartTime());
- }
-
- final int switchCode = AppOpsManager.opToSwitch(code);
- final UidState uidState = ops.uidState;
- if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, false)) {
- attributedOp.rejected(uidState.getState(), flags);
- scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- AppOpsManager.MODE_IGNORED);
- return AppOpsManager.MODE_IGNORED;
- }
- // If there is a non-default per UID policy (we set UID op mode only if
- // non-default) it takes over, otherwise use the per package policy.
- if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) {
- final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode));
- if (uidMode != AppOpsManager.MODE_ALLOWED) {
- if (DEBUG) {
- Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
- + switchCode + " (" + code + ") uid " + uid + " package "
- + packageName + " flags: " + AppOpsManager.flagsToString(flags));
- }
- attributedOp.rejected(uidState.getState(), flags);
- scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- uidMode);
- return uidMode;
- }
- } else {
- final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
- : op;
- final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode());
- if (mode != AppOpsManager.MODE_ALLOWED) {
- if (DEBUG) {
- Slog.d(TAG, "noteOperation: reject #" + mode + " for code "
- + switchCode + " (" + code + ") uid " + uid + " package "
- + packageName + " flags: " + AppOpsManager.flagsToString(flags));
- }
- attributedOp.rejected(uidState.getState(), flags);
- scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- mode);
- return mode;
- }
- }
- if (DEBUG) {
- Slog.d(TAG,
- "noteOperation: allowing code " + code + " uid " + uid + " package "
- + packageName + (attributionTag == null ? ""
- : "." + attributionTag) + " flags: "
- + AppOpsManager.flagsToString(flags));
- }
- scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- AppOpsManager.MODE_ALLOWED);
- attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag,
- uidState.getState(),
- flags);
-
- return AppOpsManager.MODE_ALLOWED;
- }
- }
-
- @Override
- public boolean isAttributionTagValid(int uid, @NonNull String packageName,
- @Nullable String attributionTag,
- @Nullable String proxyPackageName) {
- try {
- return verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName)
- .isAttributionTagValid;
- } catch (SecurityException ignored) {
- // We don't want to throw, this exception will be handled in the (c/n/s)Operation calls
- // when they need the bypass object.
- return false;
- }
- }
-
- // TODO moltmann: Allow watching for attribution ops
- @Override
- public void startWatchingActive(int[] ops, IAppOpsActiveCallback callback) {
- int watchedUid = Process.INVALID_UID;
- final int callingUid = Binder.getCallingUid();
- final int callingPid = Binder.getCallingPid();
- if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
- != PackageManager.PERMISSION_GRANTED) {
- watchedUid = callingUid;
- }
- if (ops != null) {
- Preconditions.checkArrayElementsInRange(ops, 0,
- AppOpsManager._NUM_OP - 1, "Invalid op code in: " + Arrays.toString(ops));
- }
- if (callback == null) {
- return;
- }
- synchronized (this) {
- SparseArray<ActiveCallback> callbacks = mActiveWatchers.get(callback.asBinder());
- if (callbacks == null) {
- callbacks = new SparseArray<>();
- mActiveWatchers.put(callback.asBinder(), callbacks);
- }
- final ActiveCallback activeCallback = new ActiveCallback(callback, watchedUid,
- callingUid, callingPid);
- for (int op : ops) {
- callbacks.put(op, activeCallback);
- }
- }
- }
-
- @Override
- public void stopWatchingActive(IAppOpsActiveCallback callback) {
- if (callback == null) {
- return;
- }
- synchronized (this) {
- final SparseArray<ActiveCallback> activeCallbacks =
- mActiveWatchers.remove(callback.asBinder());
- if (activeCallbacks == null) {
- return;
- }
- final int callbackCount = activeCallbacks.size();
- for (int i = 0; i < callbackCount; i++) {
- activeCallbacks.valueAt(i).destroy();
- }
- }
- }
-
- @Override
- public void startWatchingStarted(int[] ops, @NonNull IAppOpsStartedCallback callback) {
- int watchedUid = Process.INVALID_UID;
- final int callingUid = Binder.getCallingUid();
- final int callingPid = Binder.getCallingPid();
- if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
- != PackageManager.PERMISSION_GRANTED) {
- watchedUid = callingUid;
- }
-
- Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty");
- Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1,
- "Invalid op code in: " + Arrays.toString(ops));
- Objects.requireNonNull(callback, "Callback cannot be null");
-
- synchronized (this) {
- SparseArray<StartedCallback> callbacks = mStartedWatchers.get(callback.asBinder());
- if (callbacks == null) {
- callbacks = new SparseArray<>();
- mStartedWatchers.put(callback.asBinder(), callbacks);
- }
-
- final StartedCallback startedCallback = new StartedCallback(callback, watchedUid,
- callingUid, callingPid);
- for (int op : ops) {
- callbacks.put(op, startedCallback);
- }
- }
- }
-
- @Override
- public void stopWatchingStarted(IAppOpsStartedCallback callback) {
- Objects.requireNonNull(callback, "Callback cannot be null");
-
- synchronized (this) {
- final SparseArray<StartedCallback> startedCallbacks =
- mStartedWatchers.remove(callback.asBinder());
- if (startedCallbacks == null) {
- return;
- }
-
- final int callbackCount = startedCallbacks.size();
- for (int i = 0; i < callbackCount; i++) {
- startedCallbacks.valueAt(i).destroy();
- }
- }
- }
-
- @Override
- public void startWatchingNoted(@NonNull int[] ops, @NonNull IAppOpsNotedCallback callback) {
- int watchedUid = Process.INVALID_UID;
- final int callingUid = Binder.getCallingUid();
- final int callingPid = Binder.getCallingPid();
- if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
- != PackageManager.PERMISSION_GRANTED) {
- watchedUid = callingUid;
- }
- Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty");
- Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1,
- "Invalid op code in: " + Arrays.toString(ops));
- Objects.requireNonNull(callback, "Callback cannot be null");
- synchronized (this) {
- SparseArray<NotedCallback> callbacks = mNotedWatchers.get(callback.asBinder());
- if (callbacks == null) {
- callbacks = new SparseArray<>();
- mNotedWatchers.put(callback.asBinder(), callbacks);
- }
- final NotedCallback notedCallback = new NotedCallback(callback, watchedUid,
- callingUid, callingPid);
- for (int op : ops) {
- callbacks.put(op, notedCallback);
- }
- }
- }
-
- @Override
- public void stopWatchingNoted(IAppOpsNotedCallback callback) {
- Objects.requireNonNull(callback, "Callback cannot be null");
- synchronized (this) {
- final SparseArray<NotedCallback> notedCallbacks =
- mNotedWatchers.remove(callback.asBinder());
- if (notedCallbacks == null) {
- return;
- }
- final int callbackCount = notedCallbacks.size();
- for (int i = 0; i < callbackCount; i++) {
- notedCallbacks.valueAt(i).destroy();
- }
- }
- }
-
- @Override
- public int startOperation(@NonNull IBinder clientId, int code, int uid,
- @Nullable String packageName, @Nullable String attributionTag,
- boolean startIfModeDefault, @NonNull String message,
- @AttributionFlags int attributionFlags, int attributionChainId) {
- verifyIncomingUid(uid);
- verifyIncomingOp(code);
- if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
- return AppOpsManager.MODE_ERRORED;
- }
-
- String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
- if (resolvedPackageName == null) {
- return AppOpsManager.MODE_IGNORED;
- }
-
- // As a special case for OP_RECORD_AUDIO_HOTWORD, which we use only for attribution
- // purposes and not as a check, also make sure that the caller is allowed to access
- // the data gated by OP_RECORD_AUDIO.
- //
- // TODO: Revert this change before Android 12.
- if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO) {
- int result = checkOperation(OP_RECORD_AUDIO, uid, packageName, null, false);
- if (result != AppOpsManager.MODE_ALLOWED) {
- return result;
- }
- }
- return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
- Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault,
- attributionFlags, attributionChainId, /*dryRun*/ false);
- }
-
- private boolean shouldStartForMode(int mode, boolean startIfModeDefault) {
- return (mode == MODE_ALLOWED || (mode == MODE_DEFAULT && startIfModeDefault));
- }
-
- @Override
- public int startOperationUnchecked(IBinder clientId, int code, int uid,
- @NonNull String packageName, @Nullable String attributionTag, int proxyUid,
- String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags,
- boolean startIfModeDefault, @AttributionFlags int attributionFlags,
- int attributionChainId, boolean dryRun) {
- PackageVerificationResult pvr;
- try {
- pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
- if (!pvr.isAttributionTagValid) {
- attributionTag = null;
- }
- } catch (SecurityException e) {
- Slog.e(TAG, "startOperation", e);
- return AppOpsManager.MODE_ERRORED;
- }
-
- boolean isRestricted;
- int startType = START_TYPE_FAILED;
- synchronized (this) {
- final Ops ops = getOpsLocked(uid, packageName, attributionTag,
- pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
- if (ops == null) {
- if (!dryRun) {
- scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
- flags, AppOpsManager.MODE_IGNORED, startType, attributionFlags,
- attributionChainId);
- }
- if (DEBUG) {
- Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
- + " package " + packageName + " flags: "
- + AppOpsManager.flagsToString(flags));
- }
- return AppOpsManager.MODE_ERRORED;
- }
- final Op op = getOpLocked(ops, code, uid, true);
- final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
- final UidState uidState = ops.uidState;
- isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass,
- false);
- final int switchCode = AppOpsManager.opToSwitch(code);
- // If there is a non-default per UID policy (we set UID op mode only if
- // non-default) it takes over, otherwise use the per package policy.
- if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) {
- final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode));
- if (!shouldStartForMode(uidMode, startIfModeDefault)) {
- if (DEBUG) {
- Slog.d(TAG, "startOperation: uid reject #" + uidMode + " for code "
- + switchCode + " (" + code + ") uid " + uid + " package "
- + packageName + " flags: " + AppOpsManager.flagsToString(flags));
- }
- if (!dryRun) {
- attributedOp.rejected(uidState.getState(), flags);
- scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
- flags, uidMode, startType, attributionFlags, attributionChainId);
- }
- return uidMode;
- }
- } else {
- final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
- : op;
- final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode());
- if (!shouldStartForMode(mode, startIfModeDefault)) {
- if (DEBUG) {
- Slog.d(TAG, "startOperation: reject #" + mode + " for code "
- + switchCode + " (" + code + ") uid " + uid + " package "
- + packageName + " flags: " + AppOpsManager.flagsToString(flags));
- }
- if (!dryRun) {
- attributedOp.rejected(uidState.getState(), flags);
- scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
- flags, mode, startType, attributionFlags, attributionChainId);
- }
- return mode;
- }
- }
- if (DEBUG) {
- Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid
- + " package " + packageName + " restricted: " + isRestricted
- + " flags: " + AppOpsManager.flagsToString(flags));
- }
- if (!dryRun) {
- try {
- if (isRestricted) {
- attributedOp.createPaused(clientId, proxyUid, proxyPackageName,
- proxyAttributionTag, uidState.getState(), flags,
- attributionFlags, attributionChainId);
- } else {
- attributedOp.started(clientId, proxyUid, proxyPackageName,
- proxyAttributionTag, uidState.getState(), flags,
- attributionFlags, attributionChainId);
- startType = START_TYPE_STARTED;
- }
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- isRestricted ? MODE_IGNORED : MODE_ALLOWED, startType, attributionFlags,
- attributionChainId);
- }
- }
-
- // Possible bug? The raw mode could have been MODE_DEFAULT to reach here.
- return isRestricted ? MODE_IGNORED : MODE_ALLOWED;
- }
-
- @Override
- public void finishOperation(IBinder clientId, int code, int uid, String packageName,
- String attributionTag) {
- verifyIncomingUid(uid);
- verifyIncomingOp(code);
- if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
- return;
- }
-
- String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
- if (resolvedPackageName == null) {
- return;
- }
-
- finishOperationUnchecked(clientId, code, uid, resolvedPackageName, attributionTag);
- }
-
- @Override
- public void finishOperationUnchecked(IBinder clientId, int code, int uid, String packageName,
- String attributionTag) {
- PackageVerificationResult pvr;
- try {
- pvr = verifyAndGetBypass(uid, packageName, attributionTag);
- if (!pvr.isAttributionTagValid) {
- attributionTag = null;
- }
- } catch (SecurityException e) {
- Slog.e(TAG, "Cannot finishOperation", e);
- return;
- }
-
- synchronized (this) {
- Op op = getOpLocked(code, uid, packageName, attributionTag, pvr.isAttributionTagValid,
- pvr.bypass, /* edit */ true);
- if (op == null) {
- Slog.e(TAG, "Operation not found: uid=" + uid + " pkg=" + packageName + "("
- + attributionTag + ") op=" + AppOpsManager.opToName(code));
- return;
- }
- final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
- if (attributedOp == null) {
- Slog.e(TAG, "Attribution not found: uid=" + uid + " pkg=" + packageName + "("
- + attributionTag + ") op=" + AppOpsManager.opToName(code));
- return;
- }
-
- if (attributedOp.isRunning() || attributedOp.isPaused()) {
- attributedOp.finished(clientId);
- } else {
- Slog.e(TAG, "Operation not started: uid=" + uid + " pkg=" + packageName + "("
- + attributionTag + ") op=" + AppOpsManager.opToName(code));
- }
- }
- }
-
- void scheduleOpActiveChangedIfNeededLocked(int code, int uid, @NonNull String packageName,
- @Nullable String attributionTag, boolean active,
- @AttributionFlags int attributionFlags, int attributionChainId) {
- ArraySet<ActiveCallback> dispatchedCallbacks = null;
- final int callbackListCount = mActiveWatchers.size();
- for (int i = 0; i < callbackListCount; i++) {
- final SparseArray<ActiveCallback> callbacks = mActiveWatchers.valueAt(i);
- ActiveCallback callback = callbacks.get(code);
- if (callback != null) {
- if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
- continue;
- }
- if (dispatchedCallbacks == null) {
- dispatchedCallbacks = new ArraySet<>();
- }
- dispatchedCallbacks.add(callback);
- }
- }
- if (dispatchedCallbacks == null) {
- return;
- }
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsServiceImpl::notifyOpActiveChanged,
- this, dispatchedCallbacks, code, uid, packageName, attributionTag, active,
- attributionFlags, attributionChainId));
- }
-
- private void notifyOpActiveChanged(ArraySet<ActiveCallback> callbacks,
- int code, int uid, @NonNull String packageName, @Nullable String attributionTag,
- boolean active, @AttributionFlags int attributionFlags, int attributionChainId) {
- // There are features watching for mode changes such as window manager
- // and location manager which are in our process. The callbacks in these
- // features may require permissions our remote caller does not have.
- final long identity = Binder.clearCallingIdentity();
- try {
- final int callbackCount = callbacks.size();
- for (int i = 0; i < callbackCount; i++) {
- final ActiveCallback callback = callbacks.valueAt(i);
- try {
- if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
- continue;
- }
- callback.mCallback.opActiveChanged(code, uid, packageName, attributionTag,
- active, attributionFlags, attributionChainId);
- } catch (RemoteException e) {
- /* do nothing */
- }
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- void scheduleOpStartedIfNeededLocked(int code, int uid, String pkgName,
- String attributionTag, @OpFlags int flags, @Mode int result,
- @AppOpsManager.OnOpStartedListener.StartedType int startedType,
- @AttributionFlags int attributionFlags, int attributionChainId) {
- ArraySet<StartedCallback> dispatchedCallbacks = null;
- final int callbackListCount = mStartedWatchers.size();
- for (int i = 0; i < callbackListCount; i++) {
- final SparseArray<StartedCallback> callbacks = mStartedWatchers.valueAt(i);
-
- StartedCallback callback = callbacks.get(code);
- if (callback != null) {
- if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
- continue;
- }
-
- if (dispatchedCallbacks == null) {
- dispatchedCallbacks = new ArraySet<>();
- }
- dispatchedCallbacks.add(callback);
- }
- }
-
- if (dispatchedCallbacks == null) {
- return;
- }
-
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsServiceImpl::notifyOpStarted,
- this, dispatchedCallbacks, code, uid, pkgName, attributionTag, flags,
- result, startedType, attributionFlags, attributionChainId));
- }
-
- private void notifyOpStarted(ArraySet<StartedCallback> callbacks,
- int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
- @Mode int result, @AppOpsManager.OnOpStartedListener.StartedType int startedType,
- @AttributionFlags int attributionFlags, int attributionChainId) {
- final long identity = Binder.clearCallingIdentity();
- try {
- final int callbackCount = callbacks.size();
- for (int i = 0; i < callbackCount; i++) {
- final StartedCallback callback = callbacks.valueAt(i);
- try {
- if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
- continue;
- }
- callback.mCallback.opStarted(code, uid, packageName, attributionTag, flags,
- result, startedType, attributionFlags, attributionChainId);
- } catch (RemoteException e) {
- /* do nothing */
- }
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- private void scheduleOpNotedIfNeededLocked(int code, int uid, String packageName,
- String attributionTag, @OpFlags int flags, @Mode int result) {
- ArraySet<NotedCallback> dispatchedCallbacks = null;
- final int callbackListCount = mNotedWatchers.size();
- for (int i = 0; i < callbackListCount; i++) {
- final SparseArray<NotedCallback> callbacks = mNotedWatchers.valueAt(i);
- final NotedCallback callback = callbacks.get(code);
- if (callback != null) {
- if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
- continue;
- }
- if (dispatchedCallbacks == null) {
- dispatchedCallbacks = new ArraySet<>();
- }
- dispatchedCallbacks.add(callback);
- }
- }
- if (dispatchedCallbacks == null) {
- return;
- }
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsServiceImpl::notifyOpChecked,
- this, dispatchedCallbacks, code, uid, packageName, attributionTag, flags,
- result));
- }
-
- private void notifyOpChecked(ArraySet<NotedCallback> callbacks,
- int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
- @Mode int result) {
- // There are features watching for checks in our process. The callbacks in
- // these features may require permissions our remote caller does not have.
- final long identity = Binder.clearCallingIdentity();
- try {
- final int callbackCount = callbacks.size();
- for (int i = 0; i < callbackCount; i++) {
- final NotedCallback callback = callbacks.valueAt(i);
- try {
- if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
- continue;
- }
- callback.mCallback.opNoted(code, uid, packageName, attributionTag, flags,
- result);
- } catch (RemoteException e) {
- /* do nothing */
- }
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- private void verifyIncomingUid(int uid) {
- if (uid == Binder.getCallingUid()) {
- return;
- }
- if (Binder.getCallingPid() == Process.myPid()) {
- return;
- }
- mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
- Binder.getCallingPid(), Binder.getCallingUid(), null);
- }
-
- private boolean shouldIgnoreCallback(int op, int watcherPid, int watcherUid) {
- // If it's a restricted read op, ignore it if watcher doesn't have manage ops permission,
- // as watcher should not use this to signal if the value is changed.
- return opRestrictsRead(op) && mContext.checkPermission(Manifest.permission.MANAGE_APPOPS,
- watcherPid, watcherUid) != PackageManager.PERMISSION_GRANTED;
- }
-
- private void verifyIncomingOp(int op) {
- if (op >= 0 && op < AppOpsManager._NUM_OP) {
- // Enforce manage appops permission if it's a restricted read op.
- if (opRestrictsRead(op)) {
- mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
- Binder.getCallingPid(), Binder.getCallingUid(), "verifyIncomingOp");
- }
- return;
- }
- throw new IllegalArgumentException("Bad operation #" + op);
- }
-
- private boolean isIncomingPackageValid(@Nullable String packageName, @UserIdInt int userId) {
- final int callingUid = Binder.getCallingUid();
- // Handle the special UIDs that don't have actual packages (audioserver, cameraserver, etc).
- if (packageName == null || isSpecialPackage(callingUid, packageName)) {
- return true;
- }
-
- // If the package doesn't exist, #verifyAndGetBypass would throw a SecurityException in
- // the end. Although that exception would be caught and return, we could make it return
- // early.
- if (!isPackageExisted(packageName)) {
- return false;
- }
-
- if (getPackageManagerInternal().filterAppAccess(packageName, callingUid, userId)) {
- Slog.w(TAG, packageName + " not found from " + callingUid);
- return false;
- }
-
- return true;
- }
-
- private boolean isSpecialPackage(int callingUid, @Nullable String packageName) {
- final String resolvedPackage = AppOpsManager.resolvePackageName(callingUid, packageName);
- return callingUid == Process.SYSTEM_UID
- || resolveUid(resolvedPackage) != Process.INVALID_UID;
- }
-
- private @Nullable UidState getUidStateLocked(int uid, boolean edit) {
- UidState uidState = mUidStates.get(uid);
- if (uidState == null) {
- if (!edit) {
- return null;
- }
- uidState = new UidState(uid);
- mUidStates.put(uid, uidState);
- }
-
- return uidState;
- }
-
- @Override
- public void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, boolean visible) {
- synchronized (this) {
- getUidStateTracker().updateAppWidgetVisibility(uidPackageNames, visible);
- }
- }
-
- /**
- * @return {@link PackageManagerInternal}
- */
- private @NonNull PackageManagerInternal getPackageManagerInternal() {
- if (mPackageManagerInternal == null) {
- mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
- }
-
- return mPackageManagerInternal;
- }
-
- @Override
- public void verifyPackage(int uid, String packageName) {
- verifyAndGetBypass(uid, packageName, null);
- }
-
- /**
- * Create a restriction description matching the properties of the package.
- *
- * @param pkg The package to create the restriction description for
- * @return The restriction matching the package
- */
- private RestrictionBypass getBypassforPackage(@NonNull AndroidPackage pkg) {
- return new RestrictionBypass(pkg.getUid() == Process.SYSTEM_UID, pkg.isPrivileged(),
- mContext.checkPermission(android.Manifest.permission
- .EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS, -1, pkg.getUid())
- == PackageManager.PERMISSION_GRANTED);
- }
-
- /**
- * @see #verifyAndGetBypass(int, String, String, String)
- */
- private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
- @Nullable String attributionTag) {
- return verifyAndGetBypass(uid, packageName, attributionTag, null);
- }
-
- /**
- * Verify that package belongs to uid and return the {@link RestrictionBypass bypass
- * description} for the package, along with a boolean indicating whether the attribution tag is
- * valid.
- *
- * @param uid The uid the package belongs to
- * @param packageName The package the might belong to the uid
- * @param attributionTag attribution tag or {@code null} if no need to verify
- * @param proxyPackageName The proxy package, from which the attribution tag is to be pulled
- * @return PackageVerificationResult containing {@link RestrictionBypass} and whether the
- * attribution tag is valid
- */
- private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
- @Nullable String attributionTag, @Nullable String proxyPackageName) {
- if (uid == Process.ROOT_UID) {
- // For backwards compatibility, don't check package name for root UID.
- return new PackageVerificationResult(null,
- /* isAttributionTagValid */ true);
- }
- if (Process.isSdkSandboxUid(uid)) {
- // SDK sandbox processes run in their own UID range, but their associated
- // UID for checks should always be the UID of the package implementing SDK sandbox
- // service.
- // TODO: We will need to modify the callers of this function instead, so
- // modifications and checks against the app ops state are done with the
- // correct UID.
- try {
- final PackageManager pm = mContext.getPackageManager();
- final String supplementalPackageName = pm.getSdkSandboxPackageName();
- if (Objects.equals(packageName, supplementalPackageName)) {
- uid = pm.getPackageUidAsUser(supplementalPackageName,
- PackageManager.PackageInfoFlags.of(0), UserHandle.getUserId(uid));
- }
- } catch (PackageManager.NameNotFoundException e) {
- // Shouldn't happen for the supplemental package
- e.printStackTrace();
- }
- }
-
-
- // Do not check if uid/packageName/attributionTag is already known.
- synchronized (this) {
- UidState uidState = mUidStates.get(uid);
- if (uidState != null && uidState.pkgOps != null) {
- Ops ops = uidState.pkgOps.get(packageName);
-
- if (ops != null && (attributionTag == null || ops.knownAttributionTags.contains(
- attributionTag)) && ops.bypass != null) {
- return new PackageVerificationResult(ops.bypass,
- ops.validAttributionTags.contains(attributionTag));
- }
- }
- }
-
- int callingUid = Binder.getCallingUid();
-
- // Allow any attribution tag for resolvable uids
- int pkgUid;
- if (Objects.equals(packageName, "com.android.shell")) {
- // Special case for the shell which is a package but should be able
- // to bypass app attribution tag restrictions.
- pkgUid = Process.SHELL_UID;
- } else {
- pkgUid = resolveUid(packageName);
- }
- if (pkgUid != Process.INVALID_UID) {
- if (pkgUid != UserHandle.getAppId(uid)) {
- Slog.e(TAG, "Bad call made by uid " + callingUid + ". "
- + "Package \"" + packageName + "\" does not belong to uid " + uid + ".");
- String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not";
- throw new SecurityException("Specified package \"" + packageName + "\" under uid "
- + UserHandle.getAppId(uid) + otherUidMessage);
- }
- return new PackageVerificationResult(RestrictionBypass.UNRESTRICTED,
- /* isAttributionTagValid */ true);
- }
-
- int userId = UserHandle.getUserId(uid);
- RestrictionBypass bypass = null;
- boolean isAttributionTagValid = false;
-
- final long ident = Binder.clearCallingIdentity();
- try {
- PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
- AndroidPackage pkg = pmInt.getPackage(packageName);
- if (pkg != null) {
- isAttributionTagValid = isAttributionInPackage(pkg, attributionTag);
- pkgUid = UserHandle.getUid(userId, UserHandle.getAppId(pkg.getUid()));
- bypass = getBypassforPackage(pkg);
- }
- if (!isAttributionTagValid) {
- AndroidPackage proxyPkg = proxyPackageName != null
- ? pmInt.getPackage(proxyPackageName) : null;
- // Re-check in proxy.
- isAttributionTagValid = isAttributionInPackage(proxyPkg, attributionTag);
- String msg;
- if (pkg != null && isAttributionTagValid) {
- msg = "attributionTag " + attributionTag + " declared in manifest of the proxy"
- + " package " + proxyPackageName + ", this is not advised";
- } else if (pkg != null) {
- msg = "attributionTag " + attributionTag + " not declared in manifest of "
- + packageName;
- } else {
- msg = "package " + packageName + " not found, can't check for "
- + "attributionTag " + attributionTag;
- }
-
- try {
- if (!mPlatformCompat.isChangeEnabledByPackageName(
- SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE, packageName,
- userId) || !mPlatformCompat.isChangeEnabledByUid(
- SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE,
- callingUid)) {
- // Do not override tags if overriding is not enabled for this package
- isAttributionTagValid = true;
- }
- Slog.e(TAG, msg);
- } catch (RemoteException neverHappens) {
- }
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
-
- if (pkgUid != uid) {
- Slog.e(TAG, "Bad call made by uid " + callingUid + ". "
- + "Package \"" + packageName + "\" does not belong to uid " + uid + ".");
- String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not";
- throw new SecurityException("Specified package \"" + packageName + "\" under uid " + uid
- + otherUidMessage);
- }
-
- return new PackageVerificationResult(bypass, isAttributionTagValid);
- }
-
- private boolean isAttributionInPackage(@Nullable AndroidPackage pkg,
- @Nullable String attributionTag) {
- if (pkg == null) {
- return false;
- } else if (attributionTag == null) {
- return true;
- }
- if (pkg.getAttributions() != null) {
- int numAttributions = pkg.getAttributions().size();
- for (int i = 0; i < numAttributions; i++) {
- if (pkg.getAttributions().get(i).getTag().equals(attributionTag)) {
- return true;
- }
- }
- }
-
- return false;
- }
-
- /**
- * Get (and potentially create) ops.
- *
- * @param uid The uid the package belongs to
- * @param packageName The name of the package
- * @param attributionTag attribution tag
- * @param isAttributionTagValid whether the given attribution tag is valid
- * @param bypass When to bypass certain op restrictions (can be null if edit
- * == false)
- * @param edit If an ops does not exist, create the ops?
- * @return The ops
- */
- private Ops getOpsLocked(int uid, String packageName, @Nullable String attributionTag,
- boolean isAttributionTagValid, @Nullable RestrictionBypass bypass, boolean edit) {
- UidState uidState = getUidStateLocked(uid, edit);
- if (uidState == null) {
- return null;
- }
-
- if (uidState.pkgOps == null) {
- if (!edit) {
- return null;
- }
- uidState.pkgOps = new ArrayMap<>();
- }
-
- Ops ops = uidState.pkgOps.get(packageName);
- if (ops == null) {
- if (!edit) {
- return null;
- }
- ops = new Ops(packageName, uidState);
- uidState.pkgOps.put(packageName, ops);
- }
-
- if (edit) {
- if (bypass != null) {
- ops.bypass = bypass;
- }
-
- if (attributionTag != null) {
- ops.knownAttributionTags.add(attributionTag);
- if (isAttributionTagValid) {
- ops.validAttributionTags.add(attributionTag);
- } else {
- ops.validAttributionTags.remove(attributionTag);
- }
- }
- }
-
- return ops;
- }
-
- @Override
- public void scheduleWriteLocked() {
- if (!mWriteScheduled) {
- mWriteScheduled = true;
- mHandler.postDelayed(mWriteRunner, WRITE_DELAY);
- }
- }
-
- @Override
- public void scheduleFastWriteLocked() {
- if (!mFastWriteScheduled) {
- mWriteScheduled = true;
- mFastWriteScheduled = true;
- mHandler.removeCallbacks(mWriteRunner);
- mHandler.postDelayed(mWriteRunner, 10 * 1000);
- }
- }
-
- /**
- * Get the state of an op for a uid.
- *
- * @param code The code of the op
- * @param uid The uid the of the package
- * @param packageName The package name for which to get the state for
- * @param attributionTag The attribution tag
- * @param isAttributionTagValid Whether the given attribution tag is valid
- * @param bypass When to bypass certain op restrictions (can be null if edit
- * == false)
- * @param edit Iff {@code true} create the {@link Op} object if not yet created
- * @return The {@link Op state} of the op
- */
- private @Nullable Op getOpLocked(int code, int uid, @NonNull String packageName,
- @Nullable String attributionTag, boolean isAttributionTagValid,
- @Nullable RestrictionBypass bypass, boolean edit) {
- Ops ops = getOpsLocked(uid, packageName, attributionTag, isAttributionTagValid, bypass,
- edit);
- if (ops == null) {
- return null;
- }
- return getOpLocked(ops, code, uid, edit);
- }
-
- private Op getOpLocked(Ops ops, int code, int uid, boolean edit) {
- Op op = ops.get(code);
- if (op == null) {
- if (!edit) {
- return null;
- }
- op = new Op(ops.uidState, ops.packageName, code, uid);
- ops.put(code, op);
- }
- if (edit) {
- scheduleWriteLocked();
- }
- return op;
- }
-
- private boolean isOpRestrictedDueToSuspend(int code, String packageName, int uid) {
- if (!ArrayUtils.contains(OPS_RESTRICTED_ON_SUSPEND, code)) {
- return false;
- }
- final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
- return pmi.isPackageSuspended(packageName, UserHandle.getUserId(uid));
- }
-
- private boolean isOpRestrictedLocked(int uid, int code, String packageName,
- String attributionTag, @Nullable RestrictionBypass appBypass, boolean isCheckOp) {
- int restrictionSetCount = mOpGlobalRestrictions.size();
-
- for (int i = 0; i < restrictionSetCount; i++) {
- ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.valueAt(i);
- if (restrictionState.hasRestriction(code)) {
- return true;
- }
- }
-
- int userHandle = UserHandle.getUserId(uid);
- restrictionSetCount = mOpUserRestrictions.size();
-
- for (int i = 0; i < restrictionSetCount; i++) {
- // For each client, check that the given op is not restricted, or that the given
- // package is exempt from the restriction.
- ClientUserRestrictionState restrictionState = mOpUserRestrictions.valueAt(i);
- if (restrictionState.hasRestriction(code, packageName, attributionTag, userHandle,
- isCheckOp)) {
- RestrictionBypass opBypass = opAllowSystemBypassRestriction(code);
- if (opBypass != null) {
- // If we are the system, bypass user restrictions for certain codes
- synchronized (this) {
- if (opBypass.isSystemUid && appBypass != null && appBypass.isSystemUid) {
- return false;
- }
- if (opBypass.isPrivileged && appBypass != null && appBypass.isPrivileged) {
- return false;
- }
- if (opBypass.isRecordAudioRestrictionExcept && appBypass != null
- && appBypass.isRecordAudioRestrictionExcept) {
- return false;
- }
- }
- }
- return true;
- }
- }
- return false;
- }
-
- @Override
- public void readState() {
- synchronized (mFile) {
- synchronized (this) {
- FileInputStream stream;
- try {
- stream = mFile.openRead();
- } catch (FileNotFoundException e) {
- Slog.i(TAG, "No existing app ops " + mFile.getBaseFile() + "; starting empty");
- return;
- }
- boolean success = false;
- mUidStates.clear();
- mAppOpsServiceInterface.clearAllModes();
- try {
- TypedXmlPullParser parser = Xml.resolvePullParser(stream);
- int type;
- while ((type = parser.next()) != XmlPullParser.START_TAG
- && type != XmlPullParser.END_DOCUMENT) {
- // Parse next until we reach the start or end
- }
-
- if (type != XmlPullParser.START_TAG) {
- throw new IllegalStateException("no start tag found");
- }
-
- mVersionAtBoot = parser.getAttributeInt(null, "v", NO_VERSION);
-
- int outerDepth = parser.getDepth();
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
-
- String tagName = parser.getName();
- if (tagName.equals("pkg")) {
- readPackage(parser);
- } else if (tagName.equals("uid")) {
- readUidOps(parser);
- } else {
- Slog.w(TAG, "Unknown element under <app-ops>: "
- + parser.getName());
- XmlUtils.skipCurrentTag(parser);
- }
- }
- success = true;
- } catch (IllegalStateException e) {
- Slog.w(TAG, "Failed parsing " + e);
- } catch (NullPointerException e) {
- Slog.w(TAG, "Failed parsing " + e);
- } catch (NumberFormatException e) {
- Slog.w(TAG, "Failed parsing " + e);
- } catch (XmlPullParserException e) {
- Slog.w(TAG, "Failed parsing " + e);
- } catch (IOException e) {
- Slog.w(TAG, "Failed parsing " + e);
- } catch (IndexOutOfBoundsException e) {
- Slog.w(TAG, "Failed parsing " + e);
- } finally {
- if (!success) {
- mUidStates.clear();
- mAppOpsServiceInterface.clearAllModes();
- }
- try {
- stream.close();
- } catch (IOException e) {
- }
- }
- }
- }
- }
-
- @VisibleForTesting
- @GuardedBy("this")
- void upgradeRunAnyInBackgroundLocked() {
- for (int i = 0; i < mUidStates.size(); i++) {
- final UidState uidState = mUidStates.valueAt(i);
- if (uidState == null) {
- continue;
- }
- SparseIntArray opModes = uidState.getNonDefaultUidModes();
- if (opModes != null) {
- final int idx = opModes.indexOfKey(AppOpsManager.OP_RUN_IN_BACKGROUND);
- if (idx >= 0) {
- uidState.setUidMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND,
- opModes.valueAt(idx));
- }
- }
- if (uidState.pkgOps == null) {
- continue;
- }
- boolean changed = false;
- for (int j = 0; j < uidState.pkgOps.size(); j++) {
- Ops ops = uidState.pkgOps.valueAt(j);
- if (ops != null) {
- final Op op = ops.get(AppOpsManager.OP_RUN_IN_BACKGROUND);
- if (op != null && op.getMode() != AppOpsManager.opToDefaultMode(op.op)) {
- final Op copy = new Op(op.uidState, op.packageName,
- AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uidState.uid);
- copy.setMode(op.getMode());
- ops.put(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, copy);
- changed = true;
- }
- }
- }
- if (changed) {
- uidState.evalForegroundOps();
- }
- }
- }
-
- /**
- * The interpretation of the default mode - MODE_DEFAULT - for OP_SCHEDULE_EXACT_ALARM is
- * changing. Simultaneously, we want to change this op's mode from MODE_DEFAULT to MODE_ALLOWED
- * for already installed apps. For newer apps, it will stay as MODE_DEFAULT.
- */
- @VisibleForTesting
- @GuardedBy("this")
- void upgradeScheduleExactAlarmLocked() {
- final PermissionManagerServiceInternal pmsi = LocalServices.getService(
- PermissionManagerServiceInternal.class);
- final UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
- final PackageManagerInternal pmi = getPackageManagerInternal();
-
- final String[] packagesDeclaringPermission = pmsi.getAppOpPermissionPackages(
- AppOpsManager.opToPermission(OP_SCHEDULE_EXACT_ALARM));
- final int[] userIds = umi.getUserIds();
-
- for (final String pkg : packagesDeclaringPermission) {
- for (int userId : userIds) {
- final int uid = pmi.getPackageUid(pkg, 0, userId);
-
- UidState uidState = mUidStates.get(uid);
- if (uidState == null) {
- uidState = new UidState(uid);
- mUidStates.put(uid, uidState);
- }
- final int oldMode = uidState.getUidMode(OP_SCHEDULE_EXACT_ALARM);
- if (oldMode == AppOpsManager.opToDefaultMode(OP_SCHEDULE_EXACT_ALARM)) {
- uidState.setUidMode(OP_SCHEDULE_EXACT_ALARM, MODE_ALLOWED);
- }
- }
- // This appop is meant to be controlled at a uid level. So we leave package modes as
- // they are.
- }
- }
-
- @GuardedBy("this")
- private void upgradeLocked(int oldVersion) {
- if (oldVersion == NO_FILE_VERSION || oldVersion >= CURRENT_VERSION) {
- return;
- }
- Slog.d(TAG, "Upgrading app-ops xml from version " + oldVersion + " to " + CURRENT_VERSION);
- switch (oldVersion) {
- case NO_VERSION:
- upgradeRunAnyInBackgroundLocked();
- // fall through
- case 1:
- upgradeScheduleExactAlarmLocked();
- // fall through
- case 2:
- // for future upgrades
- }
- scheduleFastWriteLocked();
- }
-
- private void readUidOps(TypedXmlPullParser parser) throws NumberFormatException,
- XmlPullParserException, IOException {
- final int uid = parser.getAttributeInt(null, "n");
- int outerDepth = parser.getDepth();
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
-
- String tagName = parser.getName();
- if (tagName.equals("op")) {
- final int code = parser.getAttributeInt(null, "n");
- final int mode = parser.getAttributeInt(null, "m");
- setUidMode(code, uid, mode, null);
- } else {
- Slog.w(TAG, "Unknown element under <uid-ops>: "
- + parser.getName());
- XmlUtils.skipCurrentTag(parser);
- }
- }
- }
-
- private void readPackage(TypedXmlPullParser parser)
- throws NumberFormatException, XmlPullParserException, IOException {
- String pkgName = parser.getAttributeValue(null, "n");
- int outerDepth = parser.getDepth();
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
-
- String tagName = parser.getName();
- if (tagName.equals("uid")) {
- readUid(parser, pkgName);
- } else {
- Slog.w(TAG, "Unknown element under <pkg>: "
- + parser.getName());
- XmlUtils.skipCurrentTag(parser);
- }
- }
- }
-
- private void readUid(TypedXmlPullParser parser, String pkgName)
- throws NumberFormatException, XmlPullParserException, IOException {
- int uid = parser.getAttributeInt(null, "n");
- final UidState uidState = getUidStateLocked(uid, true);
- int outerDepth = parser.getDepth();
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
- String tagName = parser.getName();
- if (tagName.equals("op")) {
- readOp(parser, uidState, pkgName);
- } else {
- Slog.w(TAG, "Unknown element under <pkg>: "
- + parser.getName());
- XmlUtils.skipCurrentTag(parser);
- }
- }
- uidState.evalForegroundOps();
- }
-
- private void readAttributionOp(TypedXmlPullParser parser, @NonNull Op parent,
- @Nullable String attribution)
- throws NumberFormatException, IOException, XmlPullParserException {
- final AttributedOp attributedOp = parent.getOrCreateAttribution(parent, attribution);
-
- final long key = parser.getAttributeLong(null, "n");
- final int uidState = extractUidStateFromKey(key);
- final int opFlags = extractFlagsFromKey(key);
-
- final long accessTime = parser.getAttributeLong(null, "t", 0);
- final long rejectTime = parser.getAttributeLong(null, "r", 0);
- final long accessDuration = parser.getAttributeLong(null, "d", -1);
- final String proxyPkg = XmlUtils.readStringAttribute(parser, "pp");
- final int proxyUid = parser.getAttributeInt(null, "pu", Process.INVALID_UID);
- final String proxyAttributionTag = XmlUtils.readStringAttribute(parser, "pc");
-
- if (accessTime > 0) {
- attributedOp.accessed(accessTime, accessDuration, proxyUid, proxyPkg,
- proxyAttributionTag, uidState, opFlags);
- }
- if (rejectTime > 0) {
- attributedOp.rejected(rejectTime, uidState, opFlags);
- }
- }
-
- private void readOp(TypedXmlPullParser parser,
- @NonNull UidState uidState, @NonNull String pkgName)
- throws NumberFormatException, XmlPullParserException, IOException {
- int opCode = parser.getAttributeInt(null, "n");
- Op op = new Op(uidState, pkgName, opCode, uidState.uid);
-
- final int mode = parser.getAttributeInt(null, "m", AppOpsManager.opToDefaultMode(op.op));
- op.setMode(mode);
-
- int outerDepth = parser.getDepth();
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
- String tagName = parser.getName();
- if (tagName.equals("st")) {
- readAttributionOp(parser, op, XmlUtils.readStringAttribute(parser, "id"));
- } else {
- Slog.w(TAG, "Unknown element under <op>: "
- + parser.getName());
- XmlUtils.skipCurrentTag(parser);
- }
- }
-
- if (uidState.pkgOps == null) {
- uidState.pkgOps = new ArrayMap<>();
- }
- Ops ops = uidState.pkgOps.get(pkgName);
- if (ops == null) {
- ops = new Ops(pkgName, uidState);
- uidState.pkgOps.put(pkgName, ops);
- }
- ops.put(op.op, op);
- }
-
- @Override
- public void writeState() {
- synchronized (mFile) {
- FileOutputStream stream;
- try {
- stream = mFile.startWrite();
- } catch (IOException e) {
- Slog.w(TAG, "Failed to write state: " + e);
- return;
- }
-
- List<AppOpsManager.PackageOps> allOps = getPackagesForOps(null);
-
- try {
- TypedXmlSerializer out = Xml.resolveSerializer(stream);
- out.startDocument(null, true);
- out.startTag(null, "app-ops");
- out.attributeInt(null, "v", CURRENT_VERSION);
-
- SparseArray<SparseIntArray> uidStatesClone;
- synchronized (this) {
- uidStatesClone = new SparseArray<>(mUidStates.size());
-
- final int uidStateCount = mUidStates.size();
- for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) {
- UidState uidState = mUidStates.valueAt(uidStateNum);
- int uid = mUidStates.keyAt(uidStateNum);
-
- SparseIntArray opModes = uidState.getNonDefaultUidModes();
- if (opModes != null && opModes.size() > 0) {
- uidStatesClone.put(uid, opModes);
- }
- }
- }
-
- final int uidStateCount = uidStatesClone.size();
- for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) {
- SparseIntArray opModes = uidStatesClone.valueAt(uidStateNum);
- if (opModes != null && opModes.size() > 0) {
- out.startTag(null, "uid");
- out.attributeInt(null, "n", uidStatesClone.keyAt(uidStateNum));
- final int opCount = opModes.size();
- for (int opCountNum = 0; opCountNum < opCount; opCountNum++) {
- final int op = opModes.keyAt(opCountNum);
- final int mode = opModes.valueAt(opCountNum);
- out.startTag(null, "op");
- out.attributeInt(null, "n", op);
- out.attributeInt(null, "m", mode);
- out.endTag(null, "op");
- }
- out.endTag(null, "uid");
- }
- }
-
- if (allOps != null) {
- String lastPkg = null;
- for (int i = 0; i < allOps.size(); i++) {
- AppOpsManager.PackageOps pkg = allOps.get(i);
- if (!Objects.equals(pkg.getPackageName(), lastPkg)) {
- if (lastPkg != null) {
- out.endTag(null, "pkg");
- }
- lastPkg = pkg.getPackageName();
- if (lastPkg != null) {
- out.startTag(null, "pkg");
- out.attribute(null, "n", lastPkg);
- }
- }
- out.startTag(null, "uid");
- out.attributeInt(null, "n", pkg.getUid());
- List<AppOpsManager.OpEntry> ops = pkg.getOps();
- for (int j = 0; j < ops.size(); j++) {
- AppOpsManager.OpEntry op = ops.get(j);
- out.startTag(null, "op");
- out.attributeInt(null, "n", op.getOp());
- if (op.getMode() != AppOpsManager.opToDefaultMode(op.getOp())) {
- out.attributeInt(null, "m", op.getMode());
- }
-
- for (String attributionTag : op.getAttributedOpEntries().keySet()) {
- final AttributedOpEntry attribution =
- op.getAttributedOpEntries().get(attributionTag);
-
- final ArraySet<Long> keys = attribution.collectKeys();
-
- final int keyCount = keys.size();
- for (int k = 0; k < keyCount; k++) {
- final long key = keys.valueAt(k);
-
- final int uidState = AppOpsManager.extractUidStateFromKey(key);
- final int flags = AppOpsManager.extractFlagsFromKey(key);
-
- final long accessTime = attribution.getLastAccessTime(uidState,
- uidState, flags);
- final long rejectTime = attribution.getLastRejectTime(uidState,
- uidState, flags);
- final long accessDuration = attribution.getLastDuration(
- uidState, uidState, flags);
- // Proxy information for rejections is not backed up
- final OpEventProxyInfo proxy = attribution.getLastProxyInfo(
- uidState, uidState, flags);
-
- if (accessTime <= 0 && rejectTime <= 0 && accessDuration <= 0
- && proxy == null) {
- continue;
- }
-
- String proxyPkg = null;
- String proxyAttributionTag = null;
- int proxyUid = Process.INVALID_UID;
- if (proxy != null) {
- proxyPkg = proxy.getPackageName();
- proxyAttributionTag = proxy.getAttributionTag();
- proxyUid = proxy.getUid();
- }
-
- out.startTag(null, "st");
- if (attributionTag != null) {
- out.attribute(null, "id", attributionTag);
- }
- out.attributeLong(null, "n", key);
- if (accessTime > 0) {
- out.attributeLong(null, "t", accessTime);
- }
- if (rejectTime > 0) {
- out.attributeLong(null, "r", rejectTime);
- }
- if (accessDuration > 0) {
- out.attributeLong(null, "d", accessDuration);
- }
- if (proxyPkg != null) {
- out.attribute(null, "pp", proxyPkg);
- }
- if (proxyAttributionTag != null) {
- out.attribute(null, "pc", proxyAttributionTag);
- }
- if (proxyUid >= 0) {
- out.attributeInt(null, "pu", proxyUid);
- }
- out.endTag(null, "st");
- }
- }
-
- out.endTag(null, "op");
- }
- out.endTag(null, "uid");
- }
- if (lastPkg != null) {
- out.endTag(null, "pkg");
- }
- }
-
- out.endTag(null, "app-ops");
- out.endDocument();
- mFile.finishWrite(stream);
- } catch (IOException e) {
- Slog.w(TAG, "Failed to write state, restoring backup.", e);
- mFile.failWrite(stream);
- }
- }
- mHistoricalRegistry.writeAndClearDiscreteHistory();
- }
-
- private void dumpHelp(PrintWriter pw) {
- pw.println("AppOps service (appops) dump options:");
- pw.println(" -h");
- pw.println(" Print this help text.");
- pw.println(" --op [OP]");
- pw.println(" Limit output to data associated with the given app op code.");
- pw.println(" --mode [MODE]");
- pw.println(" Limit output to data associated with the given app op mode.");
- pw.println(" --package [PACKAGE]");
- pw.println(" Limit output to data associated with the given package name.");
- pw.println(" --attributionTag [attributionTag]");
- pw.println(" Limit output to data associated with the given attribution tag.");
- pw.println(" --include-discrete [n]");
- pw.println(" Include discrete ops limited to n per dimension. Use zero for no limit.");
- pw.println(" --watchers");
- pw.println(" Only output the watcher sections.");
- pw.println(" --history");
- pw.println(" Only output history.");
- pw.println(" --uid-state-changes");
- pw.println(" Include logs about uid state changes.");
- }
-
- private void dumpStatesLocked(@NonNull PrintWriter pw, @Nullable String filterAttributionTag,
- @HistoricalOpsRequestFilter int filter, long nowElapsed, @NonNull Op op, long now,
- @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix) {
- final int numAttributions = op.mAttributions.size();
- for (int i = 0; i < numAttributions; i++) {
- if ((filter & FILTER_BY_ATTRIBUTION_TAG) != 0 && !Objects.equals(
- op.mAttributions.keyAt(i), filterAttributionTag)) {
- continue;
- }
-
- pw.print(prefix + op.mAttributions.keyAt(i) + "=[\n");
- dumpStatesLocked(pw, nowElapsed, op, op.mAttributions.keyAt(i), now, sdf, date,
- prefix + " ");
- pw.print(prefix + "]\n");
- }
- }
-
- private void dumpStatesLocked(@NonNull PrintWriter pw, long nowElapsed, @NonNull Op op,
- @Nullable String attributionTag, long now, @NonNull SimpleDateFormat sdf,
- @NonNull Date date, @NonNull String prefix) {
-
- final AttributedOpEntry entry = op.createSingleAttributionEntryLocked(
- attributionTag).getAttributedOpEntries().get(attributionTag);
-
- final ArraySet<Long> keys = entry.collectKeys();
-
- final int keyCount = keys.size();
- for (int k = 0; k < keyCount; k++) {
- final long key = keys.valueAt(k);
-
- final int uidState = AppOpsManager.extractUidStateFromKey(key);
- final int flags = AppOpsManager.extractFlagsFromKey(key);
-
- final long accessTime = entry.getLastAccessTime(uidState, uidState, flags);
- final long rejectTime = entry.getLastRejectTime(uidState, uidState, flags);
- final long accessDuration = entry.getLastDuration(uidState, uidState, flags);
- final OpEventProxyInfo proxy = entry.getLastProxyInfo(uidState, uidState, flags);
-
- String proxyPkg = null;
- String proxyAttributionTag = null;
- int proxyUid = Process.INVALID_UID;
- if (proxy != null) {
- proxyPkg = proxy.getPackageName();
- proxyAttributionTag = proxy.getAttributionTag();
- proxyUid = proxy.getUid();
- }
-
- if (accessTime > 0) {
- pw.print(prefix);
- pw.print("Access: ");
- pw.print(AppOpsManager.keyToString(key));
- pw.print(" ");
- date.setTime(accessTime);
- pw.print(sdf.format(date));
- pw.print(" (");
- TimeUtils.formatDuration(accessTime - now, pw);
- pw.print(")");
- if (accessDuration > 0) {
- pw.print(" duration=");
- TimeUtils.formatDuration(accessDuration, pw);
- }
- if (proxyUid >= 0) {
- pw.print(" proxy[");
- pw.print("uid=");
- pw.print(proxyUid);
- pw.print(", pkg=");
- pw.print(proxyPkg);
- pw.print(", attributionTag=");
- pw.print(proxyAttributionTag);
- pw.print("]");
- }
- pw.println();
- }
-
- if (rejectTime > 0) {
- pw.print(prefix);
- pw.print("Reject: ");
- pw.print(AppOpsManager.keyToString(key));
- date.setTime(rejectTime);
- pw.print(sdf.format(date));
- pw.print(" (");
- TimeUtils.formatDuration(rejectTime - now, pw);
- pw.print(")");
- if (proxyUid >= 0) {
- pw.print(" proxy[");
- pw.print("uid=");
- pw.print(proxyUid);
- pw.print(", pkg=");
- pw.print(proxyPkg);
- pw.print(", attributionTag=");
- pw.print(proxyAttributionTag);
- pw.print("]");
- }
- pw.println();
- }
- }
-
- final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
- if (attributedOp.isRunning()) {
- long earliestElapsedTime = Long.MAX_VALUE;
- long maxNumStarts = 0;
- int numInProgressEvents = attributedOp.mInProgressEvents.size();
- for (int i = 0; i < numInProgressEvents; i++) {
- AttributedOp.InProgressStartOpEvent event =
- attributedOp.mInProgressEvents.valueAt(i);
-
- earliestElapsedTime = Math.min(earliestElapsedTime, event.getStartElapsedTime());
- maxNumStarts = Math.max(maxNumStarts, event.mNumUnfinishedStarts);
- }
-
- pw.print(prefix + "Running start at: ");
- TimeUtils.formatDuration(nowElapsed - earliestElapsedTime, pw);
- pw.println();
-
- if (maxNumStarts > 1) {
- pw.print(prefix + "startNesting=");
- pw.println(maxNumStarts);
- }
- }
- }
-
- @NeverCompile // Avoid size overhead of debugging code.
- @Override
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
-
- int dumpOp = OP_NONE;
- String dumpPackage = null;
- String dumpAttributionTag = null;
- int dumpUid = Process.INVALID_UID;
- int dumpMode = -1;
- boolean dumpWatchers = false;
- // TODO ntmyren: Remove the dumpHistory and dumpFilter
- boolean dumpHistory = false;
- boolean includeDiscreteOps = false;
- boolean dumpUidStateChangeLogs = false;
- int nDiscreteOps = 10;
- @HistoricalOpsRequestFilter int dumpFilter = 0;
- boolean dumpAll = false;
-
- if (args != null) {
- for (int i = 0; i < args.length; i++) {
- String arg = args[i];
- if ("-h".equals(arg)) {
- dumpHelp(pw);
- return;
- } else if ("-a".equals(arg)) {
- // dump all data
- dumpAll = true;
- } else if ("--op".equals(arg)) {
- i++;
- if (i >= args.length) {
- pw.println("No argument for --op option");
- return;
- }
- dumpOp = AppOpsService.Shell.strOpToOp(args[i], pw);
- dumpFilter |= FILTER_BY_OP_NAMES;
- if (dumpOp < 0) {
- return;
- }
- } else if ("--package".equals(arg)) {
- i++;
- if (i >= args.length) {
- pw.println("No argument for --package option");
- return;
- }
- dumpPackage = args[i];
- dumpFilter |= FILTER_BY_PACKAGE_NAME;
- try {
- dumpUid = AppGlobals.getPackageManager().getPackageUid(dumpPackage,
- PackageManager.MATCH_KNOWN_PACKAGES | PackageManager.MATCH_INSTANT,
- 0);
- } catch (RemoteException e) {
- }
- if (dumpUid < 0) {
- pw.println("Unknown package: " + dumpPackage);
- return;
- }
- dumpUid = UserHandle.getAppId(dumpUid);
- dumpFilter |= FILTER_BY_UID;
- } else if ("--attributionTag".equals(arg)) {
- i++;
- if (i >= args.length) {
- pw.println("No argument for --attributionTag option");
- return;
- }
- dumpAttributionTag = args[i];
- dumpFilter |= FILTER_BY_ATTRIBUTION_TAG;
- } else if ("--mode".equals(arg)) {
- i++;
- if (i >= args.length) {
- pw.println("No argument for --mode option");
- return;
- }
- dumpMode = AppOpsService.Shell.strModeToMode(args[i], pw);
- if (dumpMode < 0) {
- return;
- }
- } else if ("--watchers".equals(arg)) {
- dumpWatchers = true;
- } else if ("--include-discrete".equals(arg)) {
- i++;
- if (i >= args.length) {
- pw.println("No argument for --include-discrete option");
- return;
- }
- try {
- nDiscreteOps = Integer.valueOf(args[i]);
- } catch (NumberFormatException e) {
- pw.println("Wrong parameter: " + args[i]);
- return;
- }
- includeDiscreteOps = true;
- } else if ("--history".equals(arg)) {
- dumpHistory = true;
- } else if (arg.length() > 0 && arg.charAt(0) == '-') {
- pw.println("Unknown option: " + arg);
- return;
- } else if ("--uid-state-changes".equals(arg)) {
- dumpUidStateChangeLogs = true;
- } else {
- pw.println("Unknown command: " + arg);
- return;
- }
- }
- }
-
- final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
- final Date date = new Date();
- synchronized (this) {
- pw.println("Current AppOps Service state:");
- if (!dumpHistory && !dumpWatchers) {
- mConstants.dump(pw);
- }
- pw.println();
- final long now = System.currentTimeMillis();
- final long nowElapsed = SystemClock.elapsedRealtime();
- boolean needSep = false;
- if (dumpFilter == 0 && dumpMode < 0 && mProfileOwners != null && !dumpWatchers
- && !dumpHistory) {
- pw.println(" Profile owners:");
- for (int poi = 0; poi < mProfileOwners.size(); poi++) {
- pw.print(" User #");
- pw.print(mProfileOwners.keyAt(poi));
- pw.print(": ");
- UserHandle.formatUid(pw, mProfileOwners.valueAt(poi));
- pw.println();
- }
- pw.println();
- }
-
- if (!dumpHistory) {
- needSep |= mAppOpsServiceInterface.dumpListeners(dumpOp, dumpUid, dumpPackage, pw);
- }
-
- if (mModeWatchers.size() > 0 && dumpOp < 0 && !dumpHistory) {
- boolean printedHeader = false;
- for (int i = 0; i < mModeWatchers.size(); i++) {
- final ModeCallback cb = mModeWatchers.valueAt(i);
- if (dumpPackage != null
- && dumpUid != UserHandle.getAppId(cb.getWatchingUid())) {
- continue;
- }
- needSep = true;
- if (!printedHeader) {
- pw.println(" All op mode watchers:");
- printedHeader = true;
- }
- pw.print(" ");
- pw.print(Integer.toHexString(System.identityHashCode(mModeWatchers.keyAt(i))));
- pw.print(": ");
- pw.println(cb);
- }
- }
- if (mActiveWatchers.size() > 0 && dumpMode < 0) {
- needSep = true;
- boolean printedHeader = false;
- for (int watcherNum = 0; watcherNum < mActiveWatchers.size(); watcherNum++) {
- final SparseArray<ActiveCallback> activeWatchers =
- mActiveWatchers.valueAt(watcherNum);
- if (activeWatchers.size() <= 0) {
- continue;
- }
- final ActiveCallback cb = activeWatchers.valueAt(0);
- if (dumpOp >= 0 && activeWatchers.indexOfKey(dumpOp) < 0) {
- continue;
- }
- if (dumpPackage != null
- && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
- continue;
- }
- if (!printedHeader) {
- pw.println(" All op active watchers:");
- printedHeader = true;
- }
- pw.print(" ");
- pw.print(Integer.toHexString(System.identityHashCode(
- mActiveWatchers.keyAt(watcherNum))));
- pw.println(" ->");
- pw.print(" [");
- final int opCount = activeWatchers.size();
- for (int opNum = 0; opNum < opCount; opNum++) {
- if (opNum > 0) {
- pw.print(' ');
- }
- pw.print(AppOpsManager.opToName(activeWatchers.keyAt(opNum)));
- if (opNum < opCount - 1) {
- pw.print(',');
- }
- }
- pw.println("]");
- pw.print(" ");
- pw.println(cb);
- }
- }
- if (mStartedWatchers.size() > 0 && dumpMode < 0) {
- needSep = true;
- boolean printedHeader = false;
-
- final int watchersSize = mStartedWatchers.size();
- for (int watcherNum = 0; watcherNum < watchersSize; watcherNum++) {
- final SparseArray<StartedCallback> startedWatchers =
- mStartedWatchers.valueAt(watcherNum);
- if (startedWatchers.size() <= 0) {
- continue;
- }
-
- final StartedCallback cb = startedWatchers.valueAt(0);
- if (dumpOp >= 0 && startedWatchers.indexOfKey(dumpOp) < 0) {
- continue;
- }
-
- if (dumpPackage != null
- && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
- continue;
- }
-
- if (!printedHeader) {
- pw.println(" All op started watchers:");
- printedHeader = true;
- }
-
- pw.print(" ");
- pw.print(Integer.toHexString(System.identityHashCode(
- mStartedWatchers.keyAt(watcherNum))));
- pw.println(" ->");
-
- pw.print(" [");
- final int opCount = startedWatchers.size();
- for (int opNum = 0; opNum < opCount; opNum++) {
- if (opNum > 0) {
- pw.print(' ');
- }
-
- pw.print(AppOpsManager.opToName(startedWatchers.keyAt(opNum)));
- if (opNum < opCount - 1) {
- pw.print(',');
- }
- }
- pw.println("]");
-
- pw.print(" ");
- pw.println(cb);
- }
- }
- if (mNotedWatchers.size() > 0 && dumpMode < 0) {
- needSep = true;
- boolean printedHeader = false;
- for (int watcherNum = 0; watcherNum < mNotedWatchers.size(); watcherNum++) {
- final SparseArray<NotedCallback> notedWatchers =
- mNotedWatchers.valueAt(watcherNum);
- if (notedWatchers.size() <= 0) {
- continue;
- }
- final NotedCallback cb = notedWatchers.valueAt(0);
- if (dumpOp >= 0 && notedWatchers.indexOfKey(dumpOp) < 0) {
- continue;
- }
- if (dumpPackage != null
- && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
- continue;
- }
- if (!printedHeader) {
- pw.println(" All op noted watchers:");
- printedHeader = true;
- }
- pw.print(" ");
- pw.print(Integer.toHexString(System.identityHashCode(
- mNotedWatchers.keyAt(watcherNum))));
- pw.println(" ->");
- pw.print(" [");
- final int opCount = notedWatchers.size();
- for (int opNum = 0; opNum < opCount; opNum++) {
- if (opNum > 0) {
- pw.print(' ');
- }
- pw.print(AppOpsManager.opToName(notedWatchers.keyAt(opNum)));
- if (opNum < opCount - 1) {
- pw.print(',');
- }
- }
- pw.println("]");
- pw.print(" ");
- pw.println(cb);
- }
- }
- if (needSep) {
- pw.println();
- }
- for (int i = 0; i < mUidStates.size(); i++) {
- UidState uidState = mUidStates.valueAt(i);
- final SparseIntArray opModes = uidState.getNonDefaultUidModes();
- final ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
-
- if (dumpWatchers || dumpHistory) {
- continue;
- }
- if (dumpOp >= 0 || dumpPackage != null || dumpMode >= 0) {
- boolean hasOp = dumpOp < 0 || (opModes != null
- && opModes.indexOfKey(dumpOp) >= 0);
- boolean hasPackage = dumpPackage == null || dumpUid == mUidStates.keyAt(i);
- boolean hasMode = dumpMode < 0;
- if (!hasMode && opModes != null) {
- for (int opi = 0; !hasMode && opi < opModes.size(); opi++) {
- if (opModes.valueAt(opi) == dumpMode) {
- hasMode = true;
- }
- }
- }
- if (pkgOps != null) {
- for (int pkgi = 0;
- (!hasOp || !hasPackage || !hasMode) && pkgi < pkgOps.size();
- pkgi++) {
- Ops ops = pkgOps.valueAt(pkgi);
- if (!hasOp && ops != null && ops.indexOfKey(dumpOp) >= 0) {
- hasOp = true;
- }
- if (!hasMode) {
- for (int opi = 0; !hasMode && opi < ops.size(); opi++) {
- if (ops.valueAt(opi).getMode() == dumpMode) {
- hasMode = true;
- }
- }
- }
- if (!hasPackage && dumpPackage.equals(ops.packageName)) {
- hasPackage = true;
- }
- }
- }
- if (uidState.foregroundOps != null && !hasOp) {
- if (uidState.foregroundOps.indexOfKey(dumpOp) > 0) {
- hasOp = true;
- }
- }
- if (!hasOp || !hasPackage || !hasMode) {
- continue;
- }
- }
-
- pw.print(" Uid ");
- UserHandle.formatUid(pw, uidState.uid);
- pw.println(":");
- uidState.dump(pw, nowElapsed);
- if (uidState.foregroundOps != null && (dumpMode < 0
- || dumpMode == AppOpsManager.MODE_FOREGROUND)) {
- pw.println(" foregroundOps:");
- for (int j = 0; j < uidState.foregroundOps.size(); j++) {
- if (dumpOp >= 0 && dumpOp != uidState.foregroundOps.keyAt(j)) {
- continue;
- }
- pw.print(" ");
- pw.print(AppOpsManager.opToName(uidState.foregroundOps.keyAt(j)));
- pw.print(": ");
- pw.println(uidState.foregroundOps.valueAt(j) ? "WATCHER" : "SILENT");
- }
- pw.print(" hasForegroundWatchers=");
- pw.println(uidState.hasForegroundWatchers);
- }
- needSep = true;
-
- if (opModes != null) {
- final int opModeCount = opModes.size();
- for (int j = 0; j < opModeCount; j++) {
- final int code = opModes.keyAt(j);
- final int mode = opModes.valueAt(j);
- if (dumpOp >= 0 && dumpOp != code) {
- continue;
- }
- if (dumpMode >= 0 && dumpMode != mode) {
- continue;
- }
- pw.print(" ");
- pw.print(AppOpsManager.opToName(code));
- pw.print(": mode=");
- pw.println(AppOpsManager.modeToName(mode));
- }
- }
-
- if (pkgOps == null) {
- continue;
- }
-
- for (int pkgi = 0; pkgi < pkgOps.size(); pkgi++) {
- final Ops ops = pkgOps.valueAt(pkgi);
- if (dumpPackage != null && !dumpPackage.equals(ops.packageName)) {
- continue;
- }
- boolean printedPackage = false;
- for (int j = 0; j < ops.size(); j++) {
- final Op op = ops.valueAt(j);
- final int opCode = op.op;
- if (dumpOp >= 0 && dumpOp != opCode) {
- continue;
- }
- if (dumpMode >= 0 && dumpMode != op.getMode()) {
- continue;
- }
- if (!printedPackage) {
- pw.print(" Package ");
- pw.print(ops.packageName);
- pw.println(":");
- printedPackage = true;
- }
- pw.print(" ");
- pw.print(AppOpsManager.opToName(opCode));
- pw.print(" (");
- pw.print(AppOpsManager.modeToName(op.getMode()));
- final int switchOp = AppOpsManager.opToSwitch(opCode);
- if (switchOp != opCode) {
- pw.print(" / switch ");
- pw.print(AppOpsManager.opToName(switchOp));
- final Op switchObj = ops.get(switchOp);
- int mode = switchObj == null
- ? AppOpsManager.opToDefaultMode(switchOp) : switchObj.getMode();
- pw.print("=");
- pw.print(AppOpsManager.modeToName(mode));
- }
- pw.println("): ");
- dumpStatesLocked(pw, dumpAttributionTag, dumpFilter, nowElapsed, op, now,
- sdf, date, " ");
- }
- }
- }
- if (needSep) {
- pw.println();
- }
-
- boolean showUserRestrictions = !(dumpMode < 0 && !dumpWatchers && !dumpHistory);
- mAppOpsRestrictions.dumpRestrictions(pw, dumpOp, dumpPackage, showUserRestrictions);
-
- if (dumpAll || dumpUidStateChangeLogs) {
- pw.println();
- pw.println("Uid State Changes Event Log:");
- getUidStateTracker().dumpEvents(pw);
- }
- }
-
- // Must not hold the appops lock
- if (dumpHistory && !dumpWatchers) {
- mHistoricalRegistry.dump(" ", pw, dumpUid, dumpPackage, dumpAttributionTag, dumpOp,
- dumpFilter);
- }
- if (includeDiscreteOps) {
- pw.println("Discrete accesses: ");
- mHistoricalRegistry.dumpDiscreteData(pw, dumpUid, dumpPackage, dumpAttributionTag,
- dumpFilter, dumpOp, sdf, date, " ", nDiscreteOps);
- }
- }
-
- @Override
- public void setUserRestrictions(Bundle restrictions, IBinder token, int userHandle) {
- checkSystemUid("setUserRestrictions");
- Objects.requireNonNull(restrictions);
- Objects.requireNonNull(token);
- for (int i = 0; i < AppOpsManager._NUM_OP; i++) {
- String restriction = AppOpsManager.opToRestriction(i);
- if (restriction != null) {
- setUserRestrictionNoCheck(i, restrictions.getBoolean(restriction, false), token,
- userHandle, null);
- }
- }
- }
-
- @Override
- public void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle,
- PackageTagsList excludedPackageTags) {
- if (Binder.getCallingPid() != Process.myPid()) {
- mContext.enforcePermission(Manifest.permission.MANAGE_APP_OPS_RESTRICTIONS,
- Binder.getCallingPid(), Binder.getCallingUid(), null);
- }
- if (userHandle != UserHandle.getCallingUserId()) {
- if (mContext.checkCallingOrSelfPermission(Manifest.permission
- .INTERACT_ACROSS_USERS_FULL) != PackageManager.PERMISSION_GRANTED
- && mContext.checkCallingOrSelfPermission(Manifest.permission
- .INTERACT_ACROSS_USERS) != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Need INTERACT_ACROSS_USERS_FULL or"
- + " INTERACT_ACROSS_USERS to interact cross user ");
- }
- }
- verifyIncomingOp(code);
- Objects.requireNonNull(token);
- setUserRestrictionNoCheck(code, restricted, token, userHandle, excludedPackageTags);
- }
-
- private void setUserRestrictionNoCheck(int code, boolean restricted, IBinder token,
- int userHandle, PackageTagsList excludedPackageTags) {
- synchronized (AppOpsServiceImpl.this) {
- ClientUserRestrictionState restrictionState = mOpUserRestrictions.get(token);
-
- if (restrictionState == null) {
- try {
- restrictionState = new ClientUserRestrictionState(token);
- } catch (RemoteException e) {
- return;
- }
- mOpUserRestrictions.put(token, restrictionState);
- }
-
- if (restrictionState.setRestriction(code, restricted, excludedPackageTags,
- userHandle)) {
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsServiceImpl::notifyWatchersOfChange, this, code, UID_ANY));
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsServiceImpl::updateStartedOpModeForUser, this, code,
- restricted, userHandle));
- }
-
- if (restrictionState.isDefault()) {
- mOpUserRestrictions.remove(token);
- restrictionState.destroy();
- }
- }
- }
-
- @Override
- public void setGlobalRestriction(int code, boolean restricted, IBinder token) {
- if (Binder.getCallingPid() != Process.myPid()) {
- throw new SecurityException("Only the system can set global restrictions");
- }
-
- synchronized (this) {
- ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.get(token);
-
- if (restrictionState == null) {
- try {
- restrictionState = new ClientGlobalRestrictionState(token);
- } catch (RemoteException e) {
- return;
- }
- mOpGlobalRestrictions.put(token, restrictionState);
- }
-
- if (restrictionState.setRestriction(code, restricted)) {
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsServiceImpl::notifyWatchersOfChange, this, code, UID_ANY));
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsServiceImpl::updateStartedOpModeForUser, this, code,
- restricted, UserHandle.USER_ALL));
- }
-
- if (restrictionState.isDefault()) {
- mOpGlobalRestrictions.remove(token);
- restrictionState.destroy();
- }
- }
- }
-
- @Override
- public int getOpRestrictionCount(int code, UserHandle user, String pkg,
- String attributionTag) {
- int number = 0;
- synchronized (this) {
- int numRestrictions = mOpUserRestrictions.size();
- for (int i = 0; i < numRestrictions; i++) {
- if (mOpUserRestrictions.valueAt(i)
- .hasRestriction(code, pkg, attributionTag, user.getIdentifier(),
- false)) {
- number++;
- }
- }
-
- numRestrictions = mOpGlobalRestrictions.size();
- for (int i = 0; i < numRestrictions; i++) {
- if (mOpGlobalRestrictions.valueAt(i).hasRestriction(code)) {
- number++;
- }
- }
- }
-
- return number;
- }
-
- private void updateStartedOpModeForUser(int code, boolean restricted, int userId) {
- synchronized (AppOpsServiceImpl.this) {
- int numUids = mUidStates.size();
- for (int uidNum = 0; uidNum < numUids; uidNum++) {
- int uid = mUidStates.keyAt(uidNum);
- if (userId != UserHandle.USER_ALL && UserHandle.getUserId(uid) != userId) {
- continue;
- }
- updateStartedOpModeForUidLocked(code, restricted, uid);
- }
- }
- }
-
- private void updateStartedOpModeForUidLocked(int code, boolean restricted, int uid) {
- UidState uidState = mUidStates.get(uid);
- if (uidState == null || uidState.pkgOps == null) {
- return;
- }
-
- int numPkgOps = uidState.pkgOps.size();
- for (int pkgNum = 0; pkgNum < numPkgOps; pkgNum++) {
- Ops ops = uidState.pkgOps.valueAt(pkgNum);
- Op op = ops != null ? ops.get(code) : null;
- if (op == null || (op.getMode() != MODE_ALLOWED && op.getMode() != MODE_FOREGROUND)) {
- continue;
- }
- int numAttrTags = op.mAttributions.size();
- for (int attrNum = 0; attrNum < numAttrTags; attrNum++) {
- AttributedOp attrOp = op.mAttributions.valueAt(attrNum);
- if (restricted && attrOp.isRunning()) {
- attrOp.pause();
- } else if (attrOp.isPaused()) {
- attrOp.resume();
- }
- }
- }
- }
-
- @Override
- public void notifyWatchersOfChange(int code, int uid) {
- final ArraySet<OnOpModeChangedListener> modeChangedListenerSet;
- synchronized (this) {
- modeChangedListenerSet = mAppOpsServiceInterface.getOpModeChangedListeners(code);
- if (modeChangedListenerSet == null) {
- return;
- }
- }
-
- notifyOpChanged(modeChangedListenerSet, code, uid, null);
- }
-
- @Override
- public void removeUser(int userHandle) throws RemoteException {
- checkSystemUid("removeUser");
- synchronized (AppOpsServiceImpl.this) {
- final int tokenCount = mOpUserRestrictions.size();
- for (int i = tokenCount - 1; i >= 0; i--) {
- ClientUserRestrictionState opRestrictions = mOpUserRestrictions.valueAt(i);
- opRestrictions.removeUser(userHandle);
- }
- removeUidsForUserLocked(userHandle);
- }
- }
-
- @Override
- public boolean isOperationActive(int code, int uid, String packageName) {
- if (Binder.getCallingUid() != uid) {
- if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
- != PackageManager.PERMISSION_GRANTED) {
- return false;
- }
- }
- verifyIncomingOp(code);
- if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
- return false;
- }
-
- final String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
- if (resolvedPackageName == null) {
- return false;
- }
- // TODO moltmann: Allow to check for attribution op activeness
- synchronized (AppOpsServiceImpl.this) {
- Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, false, null, false);
- if (pkgOps == null) {
- return false;
- }
-
- Op op = pkgOps.get(code);
- if (op == null) {
- return false;
- }
-
- return op.isRunning();
- }
- }
-
- @Override
- public boolean isProxying(int op, @NonNull String proxyPackageName,
- @NonNull String proxyAttributionTag, int proxiedUid,
- @NonNull String proxiedPackageName) {
- Objects.requireNonNull(proxyPackageName);
- Objects.requireNonNull(proxiedPackageName);
- final long callingUid = Binder.getCallingUid();
- final long identity = Binder.clearCallingIdentity();
- try {
- final List<AppOpsManager.PackageOps> packageOps = getOpsForPackage(proxiedUid,
- proxiedPackageName, new int[]{op});
- if (packageOps == null || packageOps.isEmpty()) {
- return false;
- }
- final List<OpEntry> opEntries = packageOps.get(0).getOps();
- if (opEntries.isEmpty()) {
- return false;
- }
- final OpEntry opEntry = opEntries.get(0);
- if (!opEntry.isRunning()) {
- return false;
- }
- final OpEventProxyInfo proxyInfo = opEntry.getLastProxyInfo(
- OP_FLAG_TRUSTED_PROXIED | AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED);
- return proxyInfo != null && callingUid == proxyInfo.getUid()
- && proxyPackageName.equals(proxyInfo.getPackageName())
- && Objects.equals(proxyAttributionTag, proxyInfo.getAttributionTag());
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- @Override
- public void resetPackageOpsNoHistory(@NonNull String packageName) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
- "resetPackageOpsNoHistory");
- synchronized (AppOpsServiceImpl.this) {
- final int uid = mPackageManagerInternal.getPackageUid(packageName, 0,
- UserHandle.getCallingUserId());
- if (uid == Process.INVALID_UID) {
- return;
- }
- UidState uidState = mUidStates.get(uid);
- if (uidState == null || uidState.pkgOps == null) {
- return;
- }
- Ops removedOps = uidState.pkgOps.remove(packageName);
- mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
- if (removedOps != null) {
- scheduleFastWriteLocked();
- }
- }
- }
-
- @Override
- public void setHistoryParameters(@AppOpsManager.HistoricalMode int mode,
- long baseSnapshotInterval, int compressionStep) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
- "setHistoryParameters");
- // Must not hold the appops lock
- mHistoricalRegistry.setHistoryParameters(mode, baseSnapshotInterval, compressionStep);
- }
-
- @Override
- public void offsetHistory(long offsetMillis) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
- "offsetHistory");
- // Must not hold the appops lock
- mHistoricalRegistry.offsetHistory(offsetMillis);
- mHistoricalRegistry.offsetDiscreteHistory(offsetMillis);
- }
-
- @Override
- public void addHistoricalOps(HistoricalOps ops) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
- "addHistoricalOps");
- // Must not hold the appops lock
- mHistoricalRegistry.addHistoricalOps(ops);
- }
-
- @Override
- public void resetHistoryParameters() {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
- "resetHistoryParameters");
- // Must not hold the appops lock
- mHistoricalRegistry.resetHistoryParameters();
- }
-
- @Override
- public void clearHistory() {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
- "clearHistory");
- // Must not hold the appops lock
- mHistoricalRegistry.clearAllHistory();
- }
-
- @Override
- public void rebootHistory(long offlineDurationMillis) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
- "rebootHistory");
-
- Preconditions.checkArgument(offlineDurationMillis >= 0);
-
- // Must not hold the appops lock
- mHistoricalRegistry.shutdown();
-
- if (offlineDurationMillis > 0) {
- SystemClock.sleep(offlineDurationMillis);
- }
-
- mHistoricalRegistry = new HistoricalRegistry(mHistoricalRegistry);
- mHistoricalRegistry.systemReady(mContext.getContentResolver());
- mHistoricalRegistry.persistPendingHistory();
- }
-
- @GuardedBy("this")
- private void removeUidsForUserLocked(int userHandle) {
- for (int i = mUidStates.size() - 1; i >= 0; --i) {
- final int uid = mUidStates.keyAt(i);
- if (UserHandle.getUserId(uid) == userHandle) {
- mUidStates.valueAt(i).clear();
- mUidStates.removeAt(i);
- }
- }
- }
-
- private void checkSystemUid(String function) {
- int uid = Binder.getCallingUid();
- if (uid != Process.SYSTEM_UID) {
- throw new SecurityException(function + " must by called by the system");
- }
- }
-
- private static int resolveUid(String packageName) {
- if (packageName == null) {
- return Process.INVALID_UID;
- }
- switch (packageName) {
- case "root":
- return Process.ROOT_UID;
- case "shell":
- case "dumpstate":
- return Process.SHELL_UID;
- case "media":
- return Process.MEDIA_UID;
- case "audioserver":
- return Process.AUDIOSERVER_UID;
- case "cameraserver":
- return Process.CAMERASERVER_UID;
- }
- return Process.INVALID_UID;
- }
-
- private static String[] getPackagesForUid(int uid) {
- String[] packageNames = null;
-
- // Very early during boot the package manager is not yet or not yet fully started. At this
- // time there are no packages yet.
- if (AppGlobals.getPackageManager() != null) {
- try {
- packageNames = AppGlobals.getPackageManager().getPackagesForUid(uid);
- } catch (RemoteException e) {
- /* ignore - local call */
- }
- }
- if (packageNames == null) {
- return EmptyArray.STRING;
- }
- return packageNames;
- }
-
- private final class ClientUserRestrictionState implements DeathRecipient {
- private final IBinder mToken;
-
- ClientUserRestrictionState(IBinder token)
- throws RemoteException {
- token.linkToDeath(this, 0);
- this.mToken = token;
- }
-
- public boolean setRestriction(int code, boolean restricted,
- PackageTagsList excludedPackageTags, int userId) {
- return mAppOpsRestrictions.setUserRestriction(mToken, userId, code,
- restricted, excludedPackageTags);
- }
-
- public boolean hasRestriction(int code, String packageName, String attributionTag,
- int userId, boolean isCheckOp) {
- return mAppOpsRestrictions.getUserRestriction(mToken, userId, code, packageName,
- attributionTag, isCheckOp);
- }
-
- public void removeUser(int userId) {
- mAppOpsRestrictions.clearUserRestrictions(mToken, userId);
- }
-
- public boolean isDefault() {
- return !mAppOpsRestrictions.hasUserRestrictions(mToken);
- }
-
- @Override
- public void binderDied() {
- synchronized (AppOpsServiceImpl.this) {
- mAppOpsRestrictions.clearUserRestrictions(mToken);
- mOpUserRestrictions.remove(mToken);
- destroy();
- }
- }
-
- public void destroy() {
- mToken.unlinkToDeath(this, 0);
- }
- }
-
- private final class ClientGlobalRestrictionState implements DeathRecipient {
- final IBinder mToken;
-
- ClientGlobalRestrictionState(IBinder token)
- throws RemoteException {
- token.linkToDeath(this, 0);
- this.mToken = token;
- }
-
- boolean setRestriction(int code, boolean restricted) {
- return mAppOpsRestrictions.setGlobalRestriction(mToken, code, restricted);
- }
-
- boolean hasRestriction(int code) {
- return mAppOpsRestrictions.getGlobalRestriction(mToken, code);
- }
-
- boolean isDefault() {
- return !mAppOpsRestrictions.hasGlobalRestrictions(mToken);
- }
-
- @Override
- public void binderDied() {
- mAppOpsRestrictions.clearGlobalRestrictions(mToken);
- mOpGlobalRestrictions.remove(mToken);
- destroy();
- }
-
- void destroy() {
- mToken.unlinkToDeath(this, 0);
- }
- }
-
- @Override
- public void setDeviceAndProfileOwners(SparseIntArray owners) {
- synchronized (this) {
- mProfileOwners = owners;
- }
- }
-}
diff --git a/services/core/java/com/android/server/appop/AppOpsServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsServiceInterface.java
deleted file mode 100644
index 8420fcbd346f..000000000000
--- a/services/core/java/com/android/server/appop/AppOpsServiceInterface.java
+++ /dev/null
@@ -1,494 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.appop;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.AppOpsManager;
-import android.content.AttributionSource;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.PackageTagsList;
-import android.os.RemoteCallback;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.SparseArray;
-import android.util.SparseIntArray;
-
-import com.android.internal.app.IAppOpsActiveCallback;
-import com.android.internal.app.IAppOpsCallback;
-import com.android.internal.app.IAppOpsNotedCallback;
-import com.android.internal.app.IAppOpsStartedCallback;
-
-import dalvik.annotation.optimization.NeverCompile;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.List;
-
-/**
- *
- */
-public interface AppOpsServiceInterface extends PersistenceScheduler {
-
- /**
- *
- */
- void systemReady();
-
- /**
- *
- */
- void shutdown();
-
- /**
- *
- * @param uid
- * @param packageName
- */
- void verifyPackage(int uid, String packageName);
-
- /**
- *
- * @param op
- * @param packageName
- * @param flags
- * @param callback
- */
- void startWatchingModeWithFlags(int op, String packageName, int flags,
- IAppOpsCallback callback);
-
- /**
- *
- * @param callback
- */
- void stopWatchingMode(IAppOpsCallback callback);
-
- /**
- *
- * @param ops
- * @param callback
- */
- void startWatchingActive(int[] ops, IAppOpsActiveCallback callback);
-
- /**
- *
- * @param callback
- */
- void stopWatchingActive(IAppOpsActiveCallback callback);
-
- /**
- *
- * @param ops
- * @param callback
- */
- void startWatchingStarted(int[] ops, @NonNull IAppOpsStartedCallback callback);
-
- /**
- *
- * @param callback
- */
- void stopWatchingStarted(IAppOpsStartedCallback callback);
-
- /**
- *
- * @param ops
- * @param callback
- */
- void startWatchingNoted(@NonNull int[] ops, @NonNull IAppOpsNotedCallback callback);
-
- /**
- *
- * @param callback
- */
- void stopWatchingNoted(IAppOpsNotedCallback callback);
-
- /**
- * @param clientId
- * @param code
- * @param uid
- * @param packageName
- * @param attributionTag
- * @param startIfModeDefault
- * @param message
- * @param attributionFlags
- * @param attributionChainId
- * @return
- */
- int startOperation(@NonNull IBinder clientId, int code, int uid,
- @Nullable String packageName, @Nullable String attributionTag,
- boolean startIfModeDefault, @NonNull String message,
- @AppOpsManager.AttributionFlags int attributionFlags,
- int attributionChainId);
-
-
- int startOperationUnchecked(IBinder clientId, int code, int uid, @NonNull String packageName,
- @Nullable String attributionTag, int proxyUid, String proxyPackageName,
- @Nullable String proxyAttributionTag, @AppOpsManager.OpFlags int flags,
- boolean startIfModeDefault, @AppOpsManager.AttributionFlags int attributionFlags,
- int attributionChainId, boolean dryRun);
-
- /**
- *
- * @param clientId
- * @param code
- * @param uid
- * @param packageName
- * @param attributionTag
- */
- void finishOperation(IBinder clientId, int code, int uid, String packageName,
- String attributionTag);
-
- /**
- *
- * @param clientId
- * @param code
- * @param uid
- * @param packageName
- * @param attributionTag
- */
- void finishOperationUnchecked(IBinder clientId, int code, int uid, String packageName,
- String attributionTag);
-
- /**
- *
- * @param uidPackageNames
- * @param visible
- */
- void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, boolean visible);
-
- /**
- *
- */
- void readState();
-
- /**
- *
- */
- void writeState();
-
- /**
- *
- * @param uid
- * @param packageName
- */
- void packageRemoved(int uid, String packageName);
-
- /**
- *
- * @param uid
- */
- void uidRemoved(int uid);
-
- /**
- *
- * @param uid
- * @param procState
- * @param capability
- */
- void updateUidProcState(int uid, int procState,
- @ActivityManager.ProcessCapability int capability);
-
- /**
- *
- * @param ops
- * @return
- */
- List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops);
-
- /**
- *
- * @param uid
- * @param packageName
- * @param ops
- * @return
- */
- List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName,
- int[] ops);
-
- /**
- *
- * @param uid
- * @param packageName
- * @param attributionTag
- * @param opNames
- * @param dataType
- * @param filter
- * @param beginTimeMillis
- * @param endTimeMillis
- * @param flags
- * @param callback
- */
- void getHistoricalOps(int uid, String packageName, String attributionTag,
- List<String> opNames, int dataType, int filter, long beginTimeMillis,
- long endTimeMillis, int flags, RemoteCallback callback);
-
- /**
- *
- * @param uid
- * @param packageName
- * @param attributionTag
- * @param opNames
- * @param dataType
- * @param filter
- * @param beginTimeMillis
- * @param endTimeMillis
- * @param flags
- * @param callback
- */
- void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag,
- List<String> opNames, int dataType, int filter, long beginTimeMillis,
- long endTimeMillis, int flags, RemoteCallback callback);
-
- /**
- *
- */
- void reloadNonHistoricalState();
-
- /**
- *
- * @param uid
- * @param ops
- * @return
- */
- List<AppOpsManager.PackageOps> getUidOps(int uid, int[] ops);
-
- /**
- *
- * @param owners
- */
- void setDeviceAndProfileOwners(SparseIntArray owners);
-
- // used in audio restriction calls, might just copy the logic to avoid having this call.
- /**
- *
- * @param callingPid
- * @param callingUid
- * @param targetUid
- */
- void enforceManageAppOpsModes(int callingPid, int callingUid, int targetUid);
-
- /**
- *
- * @param code
- * @param uid
- * @param mode
- * @param permissionPolicyCallback
- */
- void setUidMode(int code, int uid, int mode,
- @Nullable IAppOpsCallback permissionPolicyCallback);
-
- /**
- *
- * @param code
- * @param uid
- * @param packageName
- * @param mode
- * @param permissionPolicyCallback
- */
- void setMode(int code, int uid, @NonNull String packageName, int mode,
- @Nullable IAppOpsCallback permissionPolicyCallback);
-
- /**
- *
- * @param reqUserId
- * @param reqPackageName
- */
- void resetAllModes(int reqUserId, String reqPackageName);
-
- /**
- *
- * @param code
- * @param uid
- * @param packageName
- * @param attributionTag
- * @param raw
- * @return
- */
- int checkOperation(int code, int uid, String packageName,
- @Nullable String attributionTag, boolean raw);
-
- /**
- *
- * @param uid
- * @param packageName
- * @return
- */
- int checkPackage(int uid, String packageName);
-
- /**
- *
- * @param code
- * @param uid
- * @param packageName
- * @param attributionTag
- * @param message
- * @return
- */
- int noteOperation(int code, int uid, @Nullable String packageName,
- @Nullable String attributionTag, @Nullable String message);
-
- /**
- *
- * @param code
- * @param uid
- * @param packageName
- * @param attributionTag
- * @param proxyUid
- * @param proxyPackageName
- * @param proxyAttributionTag
- * @param flags
- * @return
- */
- @AppOpsManager.Mode
- int noteOperationUnchecked(int code, int uid, @NonNull String packageName,
- @Nullable String attributionTag, int proxyUid, String proxyPackageName,
- @Nullable String proxyAttributionTag, @AppOpsManager.OpFlags int flags);
-
- boolean isAttributionTagValid(int uid, @NonNull String packageName,
- @Nullable String attributionTag, @Nullable String proxyPackageName);
-
- /**
- *
- * @param fd
- * @param pw
- * @param args
- */
- @NeverCompile
- // Avoid size overhead of debugging code.
- void dump(FileDescriptor fd, PrintWriter pw, String[] args);
-
- /**
- *
- * @param restrictions
- * @param token
- * @param userHandle
- */
- void setUserRestrictions(Bundle restrictions, IBinder token, int userHandle);
-
- /**
- *
- * @param code
- * @param restricted
- * @param token
- * @param userHandle
- * @param excludedPackageTags
- */
- void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle,
- PackageTagsList excludedPackageTags);
-
- /**
- *
- * @param code
- * @param restricted
- * @param token
- */
- void setGlobalRestriction(int code, boolean restricted, IBinder token);
-
- /**
- *
- * @param code
- * @param user
- * @param pkg
- * @param attributionTag
- * @return
- */
- int getOpRestrictionCount(int code, UserHandle user, String pkg,
- String attributionTag);
-
- /**
- *
- * @param code
- * @param uid
- */
- // added to interface for audio restriction stuff
- void notifyWatchersOfChange(int code, int uid);
-
- /**
- *
- * @param userHandle
- * @throws RemoteException
- */
- void removeUser(int userHandle) throws RemoteException;
-
- /**
- *
- * @param code
- * @param uid
- * @param packageName
- * @return
- */
- boolean isOperationActive(int code, int uid, String packageName);
-
- /**
- *
- * @param op
- * @param proxyPackageName
- * @param proxyAttributionTag
- * @param proxiedUid
- * @param proxiedPackageName
- * @return
- */
- // TODO this one might not need to be in the interface
- boolean isProxying(int op, @NonNull String proxyPackageName,
- @NonNull String proxyAttributionTag, int proxiedUid,
- @NonNull String proxiedPackageName);
-
- /**
- *
- * @param packageName
- */
- void resetPackageOpsNoHistory(@NonNull String packageName);
-
- /**
- *
- * @param mode
- * @param baseSnapshotInterval
- * @param compressionStep
- */
- void setHistoryParameters(@AppOpsManager.HistoricalMode int mode,
- long baseSnapshotInterval, int compressionStep);
-
- /**
- *
- * @param offsetMillis
- */
- void offsetHistory(long offsetMillis);
-
- /**
- *
- * @param ops
- */
- void addHistoricalOps(AppOpsManager.HistoricalOps ops);
-
- /**
- *
- */
- void resetHistoryParameters();
-
- /**
- *
- */
- void clearHistory();
-
- /**
- *
- * @param offlineDurationMillis
- */
- void rebootHistory(long offlineDurationMillis);
-}
diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
index c1434e4d9f4d..5114bd59f084 100644
--- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
@@ -59,7 +59,7 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
private final DelayableExecutor mExecutor;
private final Clock mClock;
private ActivityManagerInternal mActivityManagerInternal;
- private AppOpsServiceImpl.Constants mConstants;
+ private AppOpsService.Constants mConstants;
private SparseIntArray mUidStates = new SparseIntArray();
private SparseIntArray mPendingUidStates = new SparseIntArray();
@@ -85,7 +85,7 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
AppOpsUidStateTrackerImpl(ActivityManagerInternal activityManagerInternal,
Handler handler, Executor lockingExecutor, Clock clock,
- AppOpsServiceImpl.Constants constants) {
+ AppOpsService.Constants constants) {
this(activityManagerInternal, new DelayableExecutor() {
@Override
@@ -102,7 +102,7 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
@VisibleForTesting
AppOpsUidStateTrackerImpl(ActivityManagerInternal activityManagerInternal,
- DelayableExecutor executor, Clock clock, AppOpsServiceImpl.Constants constants,
+ DelayableExecutor executor, Clock clock, AppOpsService.Constants constants,
Thread executorThread) {
mActivityManagerInternal = activityManagerInternal;
mExecutor = executor;
diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java
index 797026908619..dcc36bcf6149 100644
--- a/services/core/java/com/android/server/appop/AttributedOp.java
+++ b/services/core/java/com/android/server/appop/AttributedOp.java
@@ -40,9 +40,9 @@ import java.util.List;
import java.util.NoSuchElementException;
final class AttributedOp {
- private final @NonNull AppOpsServiceImpl mAppOpsService;
+ private final @NonNull AppOpsService mAppOpsService;
public final @Nullable String tag;
- public final @NonNull AppOpsServiceImpl.Op parent;
+ public final @NonNull AppOpsService.Op parent;
/**
* Last successful accesses (noteOp + finished startOp) for each uidState/opFlag combination
@@ -80,8 +80,8 @@ final class AttributedOp {
// @GuardedBy("mAppOpsService")
@Nullable ArrayMap<IBinder, InProgressStartOpEvent> mPausedInProgressEvents;
- AttributedOp(@NonNull AppOpsServiceImpl appOpsService, @Nullable String tag,
- @NonNull AppOpsServiceImpl.Op parent) {
+ AttributedOp(@NonNull AppOpsService appOpsService, @Nullable String tag,
+ @NonNull AppOpsService.Op parent) {
mAppOpsService = appOpsService;
this.tag = tag;
this.parent = parent;
@@ -131,8 +131,8 @@ final class AttributedOp {
AppOpsManager.OpEventProxyInfo proxyInfo = null;
if (proxyUid != Process.INVALID_UID) {
- proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid,
- proxyPackageName, proxyAttributionTag);
+ proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
+ proxyAttributionTag);
}
AppOpsManager.NoteOpEvent existingEvent = mAccessEvents.get(key);
@@ -238,7 +238,7 @@ final class AttributedOp {
if (event == null) {
event = mAppOpsService.mInProgressStartOpEventPool.acquire(startTime,
SystemClock.elapsedRealtime(), clientId, tag,
- PooledLambda.obtainRunnable(AppOpsServiceImpl::onClientDeath, this, clientId),
+ PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId),
proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags,
attributionFlags, attributionChainId);
events.put(clientId, event);
@@ -251,9 +251,9 @@ final class AttributedOp {
event.mNumUnfinishedStarts++;
if (isStarted) {
- mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op,
- parent.uid, parent.packageName, tag, uidState, flags, startTime,
- attributionFlags, attributionChainId);
+ mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
+ parent.packageName, tag, uidState, flags, startTime, attributionFlags,
+ attributionChainId);
}
}
@@ -309,8 +309,8 @@ final class AttributedOp {
mAccessEvents.put(makeKey(event.getUidState(), event.getFlags()),
finishedEvent);
- mAppOpsService.mHistoricalRegistry.increaseOpAccessDuration(parent.op,
- parent.uid, parent.packageName, tag, event.getUidState(),
+ mAppOpsService.mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid,
+ parent.packageName, tag, event.getUidState(),
event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration(),
event.getAttributionFlags(), event.getAttributionChainId());
@@ -334,13 +334,13 @@ final class AttributedOp {
@SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
private void finishPossiblyPaused(@NonNull IBinder clientId, boolean isPausing) {
if (!isPaused()) {
- Slog.wtf(AppOpsServiceImpl.TAG, "No ops running or paused");
+ Slog.wtf(AppOpsService.TAG, "No ops running or paused");
return;
}
int indexOfToken = mPausedInProgressEvents.indexOfKey(clientId);
if (indexOfToken < 0) {
- Slog.wtf(AppOpsServiceImpl.TAG, "No op running or paused for the client");
+ Slog.wtf(AppOpsService.TAG, "No op running or paused for the client");
return;
} else if (isPausing) {
// already paused
@@ -416,9 +416,9 @@ final class AttributedOp {
mInProgressEvents.put(event.getClientId(), event);
event.setStartElapsedTime(SystemClock.elapsedRealtime());
event.setStartTime(startTime);
- mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op,
- parent.uid, parent.packageName, tag, event.getUidState(), event.getFlags(),
- startTime, event.getAttributionFlags(), event.getAttributionChainId());
+ mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
+ parent.packageName, tag, event.getUidState(), event.getFlags(), startTime,
+ event.getAttributionFlags(), event.getAttributionChainId());
if (shouldSendActive) {
mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
parent.packageName, tag, true, event.getAttributionFlags(),
@@ -503,8 +503,8 @@ final class AttributedOp {
newEvent.mNumUnfinishedStarts += numPreviousUnfinishedStarts - 1;
}
} catch (RemoteException e) {
- if (AppOpsServiceImpl.DEBUG) {
- Slog.e(AppOpsServiceImpl.TAG,
+ if (AppOpsService.DEBUG) {
+ Slog.e(AppOpsService.TAG,
"Cannot switch to new uidState " + newState);
}
}
@@ -555,8 +555,8 @@ final class AttributedOp {
ArrayMap<IBinder, InProgressStartOpEvent> ignoredEvents =
opToAdd.isRunning()
? opToAdd.mInProgressEvents : opToAdd.mPausedInProgressEvents;
- Slog.w(AppOpsServiceImpl.TAG, "Ignoring " + ignoredEvents.size()
- + " app-ops, running: " + opToAdd.isRunning());
+ Slog.w(AppOpsService.TAG, "Ignoring " + ignoredEvents.size() + " app-ops, running: "
+ + opToAdd.isRunning());
int numInProgressEvents = ignoredEvents.size();
for (int i = 0; i < numInProgressEvents; i++) {
@@ -668,22 +668,16 @@ final class AttributedOp {
/**
* Create a new {@link InProgressStartOpEvent}.
*
- * @param startTime The time {@link AppOpCheckingServiceInterface#startOperation}
- * was called
- * @param startElapsedTime The elapsed time whe
- * {@link AppOpCheckingServiceInterface#startOperation} was called
- * @param clientId The client id of the caller of
- * {@link AppOpCheckingServiceInterface#startOperation}
+ * @param startTime The time {@link #startOperation} was called
+ * @param startElapsedTime The elapsed time when {@link #startOperation} was called
+ * @param clientId The client id of the caller of {@link #startOperation}
* @param attributionTag The attribution tag for the operation.
* @param onDeath The code to execute on client death
- * @param uidState The uidstate of the app
- * {@link AppOpCheckingServiceInterface#startOperation} was called
- * for
+ * @param uidState The uidstate of the app {@link #startOperation} was called for
* @param attributionFlags the attribution flags for this operation.
* @param attributionChainId the unique id of the attribution chain this op is a part of.
- * @param proxy The proxy information, if
- * {@link AppOpCheckingServiceInterface#startProxyOperation} was
- * called
+ * @param proxy The proxy information, if {@link #startProxyOperation} was
+ * called
* @param flags The trusted/nontrusted/self flags.
* @throws RemoteException If the client is dying
*/
@@ -724,21 +718,15 @@ final class AttributedOp {
/**
* Reinit existing object with new state.
*
- * @param startTime The time {@link AppOpCheckingServiceInterface#startOperation}
- * was called
- * @param startElapsedTime The elapsed time when
- * {@link AppOpCheckingServiceInterface#startOperation} was called
- * @param clientId The client id of the caller of
- * {@link AppOpCheckingServiceInterface#startOperation}
+ * @param startTime The time {@link #startOperation} was called
+ * @param startElapsedTime The elapsed time when {@link #startOperation} was called
+ * @param clientId The client id of the caller of {@link #startOperation}
* @param attributionTag The attribution tag for this operation.
* @param onDeath The code to execute on client death
- * @param uidState The uidstate of the app
- * {@link AppOpCheckingServiceInterface#startOperation} was called
- * for
+ * @param uidState The uidstate of the app {@link #startOperation} was called for
* @param flags The flags relating to the proxy
- * @param proxy The proxy information, if
- * {@link AppOpCheckingServiceInterface#startProxyOperation was
- * called
+ * @param proxy The proxy information, if {@link #startProxyOperation}
+ * was called
* @param attributionFlags the attribution flags for this operation.
* @param attributionChainId the unique id of the attribution chain this op is a part of.
* @param proxyPool The pool to release
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index fb6511c7b321..9b433cf654b0 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -10003,6 +10003,7 @@ public class AudioService extends IAudioService.Stub
static final int LOG_NB_EVENTS_VOLUME = 40;
static final int LOG_NB_EVENTS_DYN_POLICY = 10;
static final int LOG_NB_EVENTS_SPATIAL = 30;
+ static final int LOG_NB_EVENTS_SOUND_DOSE = 30;
static final EventLogger
sLifecycleLogger = new EventLogger(LOG_NB_EVENTS_LIFECYCLE,
@@ -11419,6 +11420,11 @@ public class AudioService extends IAudioService.Stub
public void onStop() {
unregisterAudioPolicyAsync(mPolicyCallback);
}
+
+ @Override
+ public void onCapturedContentResize(int width, int height) {
+ // Ignore resize of the captured content.
+ }
};
UnregisterOnStopCallback mProjectionCallback;
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index b920517166ec..d30bec70ce9e 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -476,4 +476,53 @@ public class AudioServiceEvents {
}
}
}
+
+ static final class SoundDoseEvent extends EventLogger.Event {
+ static final int MOMENTARY_EXPOSURE = 0;
+ static final int DOSE_UPDATE = 1;
+ static final int DOSE_REPEAT_5X = 2;
+ static final int DOSE_ACCUMULATION_START = 3;
+ final int mEventType;
+ final float mFloatValue;
+ final long mLongValue;
+
+ private SoundDoseEvent(int event, float f, long l) {
+ mEventType = event;
+ mFloatValue = f;
+ mLongValue = l;
+ }
+
+ static SoundDoseEvent getMomentaryExposureEvent(float mel) {
+ return new SoundDoseEvent(MOMENTARY_EXPOSURE, mel, 0 /*ignored*/);
+ }
+
+ static SoundDoseEvent getDoseUpdateEvent(float csd, long totalDuration) {
+ return new SoundDoseEvent(DOSE_UPDATE, csd, totalDuration);
+ }
+
+ static SoundDoseEvent getDoseRepeat5xEvent() {
+ return new SoundDoseEvent(DOSE_REPEAT_5X, 0 /*ignored*/, 0 /*ignored*/);
+ }
+
+ static SoundDoseEvent getDoseAccumulationStartEvent() {
+ return new SoundDoseEvent(DOSE_ACCUMULATION_START, 0 /*ignored*/, 0 /*ignored*/);
+ }
+
+ @Override
+ public String eventToString() {
+ switch (mEventType) {
+ case MOMENTARY_EXPOSURE:
+ return String.format("momentary exposure MEL=%.2f", mFloatValue);
+ case DOSE_UPDATE:
+ return String.format(java.util.Locale.US,
+ "dose update CSD=%.1f%% total duration=%d",
+ mFloatValue * 100.0f, mLongValue);
+ case DOSE_REPEAT_5X:
+ return "CSD reached 500%";
+ case DOSE_ACCUMULATION_START:
+ return "CSD accumulating: RS2 entered";
+ }
+ return new StringBuilder("FIXME invalid event type:").append(mEventType).toString();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index 0d0de8ad2886..5fe9ada8c9cc 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -43,6 +43,8 @@ import android.util.MathUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.server.audio.AudioService.AudioHandler;
import com.android.server.audio.AudioService.ISafeHearingVolumeController;
+import com.android.server.audio.AudioServiceEvents.SoundDoseEvent;
+import com.android.server.utils.EventLogger;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -94,6 +96,9 @@ public class SoundDoseHelper {
private static final float CUSTOM_RS2_VALUE = 90;
+ private final EventLogger mLogger = new EventLogger(AudioService.LOG_NB_EVENTS_SOUND_DOSE,
+ "CSD updates");
+
private int mMcc = 0;
final Object mSafeMediaVolumeStateLock = new Object();
@@ -147,17 +152,21 @@ public class SoundDoseHelper {
public void onMomentaryExposure(float currentMel, int deviceId) {
Log.w(TAG, "DeviceId " + deviceId + " triggered momentary exposure with value: "
+ currentMel);
+ mLogger.enqueue(SoundDoseEvent.getMomentaryExposureEvent(currentMel));
}
public void onNewCsdValue(float currentCsd, SoundDoseRecord[] records) {
Log.i(TAG, "onNewCsdValue: " + currentCsd);
mCurrentCsd = currentCsd;
mDoseRecords.addAll(Arrays.asList(records));
+ long totalDuration = 0;
for (SoundDoseRecord record : records) {
Log.i(TAG, " new record: csd=" + record.value
+ " averageMel=" + record.averageMel + " timestamp=" + record.timestamp
+ " duration=" + record.duration);
+ totalDuration += record.duration;
}
+ mLogger.enqueue(SoundDoseEvent.getDoseUpdateEvent(currentCsd, totalDuration));
}
};
@@ -400,6 +409,9 @@ public class SoundDoseHelper {
pw.print(" mMusicActiveMs="); pw.println(mMusicActiveMs);
pw.print(" mMcc="); pw.println(mMcc);
pw.print(" mPendingVolumeCommand="); pw.println(mPendingVolumeCommand);
+ pw.println();
+ mLogger.dump(pw);
+ pw.println();
}
/*package*/void reset() {
diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java
index 372bc8ad94a1..a4bd6a699ceb 100644
--- a/services/core/java/com/android/server/display/ColorFade.java
+++ b/services/core/java/com/android/server/display/ColorFade.java
@@ -16,7 +16,7 @@
package com.android.server.display;
-import static com.android.server.wm.utils.RotationAnimationUtils.hasProtectedContent;
+import static com.android.internal.policy.TransitionAnimation.hasProtectedContent;
import android.content.Context;
import android.graphics.BLASTBufferQueue;
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index c700ccbdc0a1..329e3ca0d5b9 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1852,15 +1852,7 @@ public final class DisplayManagerService extends SystemService {
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.USER_PREFERRED_RESOLUTION_WIDTH, resolutionWidth);
mDisplayDeviceRepo.forEachLocked((DisplayDevice device) -> {
- // If there is a display specific mode, don't override that
- final Point deviceUserPreferredResolution =
- mPersistentDataStore.getUserPreferredResolution(device);
- final float deviceRefreshRate =
- mPersistentDataStore.getUserPreferredRefreshRate(device);
- if (!isValidResolution(deviceUserPreferredResolution)
- && !isValidRefreshRate(deviceRefreshRate)) {
- device.setUserPreferredDisplayModeLocked(mode);
- }
+ device.setUserPreferredDisplayModeLocked(mode);
});
}
@@ -3723,44 +3715,21 @@ public final class DisplayManagerService extends SystemService {
@Override
public Set<DisplayInfo> getPossibleDisplayInfo(int displayId) {
synchronized (mSyncRoot) {
- // Retrieve the group associated with this display id.
- final int displayGroupId =
- mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(displayId);
- if (displayGroupId == Display.INVALID_DISPLAY_GROUP) {
- Slog.w(TAG,
- "Can't get possible display info since display group for " + displayId
- + " does not exist");
- return new ArraySet<>();
- }
-
- // Assume any display in this group can be swapped out for the given display id.
Set<DisplayInfo> possibleInfo = new ArraySet<>();
- final DisplayGroup group = mLogicalDisplayMapper.getDisplayGroupLocked(
- displayGroupId);
- for (int i = 0; i < group.getSizeLocked(); i++) {
- final int id = group.getIdLocked(i);
- final LogicalDisplay logical = mLogicalDisplayMapper.getDisplayLocked(id);
- if (logical == null) {
- Slog.w(TAG,
- "Can't get possible display info since logical display for "
- + "display id " + id + " does not exist, as part of group "
- + displayGroupId);
- } else {
- possibleInfo.add(logical.getDisplayInfoLocked());
- }
- }
-
- // For the supported device states, retrieve the DisplayInfos for the logical
- // display layout.
+ // For each of supported device states, retrieve the display layout of that state,
+ // and return all of the DisplayInfos (one per state) for the given display id.
if (mDeviceStateManager == null) {
Slog.w(TAG, "Can't get supported states since DeviceStateManager not ready");
- } else {
- final int[] supportedStates =
- mDeviceStateManager.getSupportedStateIdentifiers();
- for (int state : supportedStates) {
- possibleInfo.addAll(
- mLogicalDisplayMapper.getDisplayInfoForStateLocked(state, displayId,
- displayGroupId));
+ return possibleInfo;
+ }
+ final int[] supportedStates =
+ mDeviceStateManager.getSupportedStateIdentifiers();
+ DisplayInfo displayInfo;
+ for (int state : supportedStates) {
+ displayInfo = mLogicalDisplayMapper.getDisplayInfoForStateLocked(state,
+ displayId);
+ if (displayInfo != null) {
+ possibleInfo.add(displayInfo);
}
}
return possibleInfo;
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 9d478927edd5..75415cd9997f 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -453,6 +453,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
// PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary adjustment set.
private float mTemporaryAutoBrightnessAdjustment;
+ private boolean mUseAutoBrightness;
+
private boolean mIsRbcActive;
// Whether there's a callback to tell listeners the display has changed scheduled to run. When
@@ -683,6 +685,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
@Override
public void onSwitchUser(@UserIdInt int newUserId) {
handleSettingsChange(true /* userSwitch */);
+ handleBrightnessModeChange();
if (mBrightnessTracker != null) {
mBrightnessTracker.onSwitchUser(newUserId);
}
@@ -930,6 +933,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mContext.getContentResolver().registerContentObserver(
Settings.System.getUriFor(Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ),
false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE),
+ false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
+ handleBrightnessModeChange();
}
private void setUpAutoBrightness(Resources resources, Handler handler) {
@@ -1335,11 +1342,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
final boolean autoBrightnessEnabledInDoze =
mAllowAutoBrightnessWhileDozingConfig && Display.isDozeState(state);
- final boolean autoBrightnessEnabled = mPowerRequest.useAutoBrightness
+ final boolean autoBrightnessEnabled = mUseAutoBrightness
&& (state == Display.STATE_ON || autoBrightnessEnabledInDoze)
&& Float.isNaN(brightnessState)
&& mAutomaticBrightnessController != null;
- final boolean autoBrightnessDisabledDueToDisplayOff = mPowerRequest.useAutoBrightness
+ final boolean autoBrightnessDisabledDueToDisplayOff = mUseAutoBrightness
&& !(state == Display.STATE_ON || autoBrightnessEnabledInDoze);
final int autoBrightnessState = autoBrightnessEnabled
? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
@@ -1691,7 +1698,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
|| brightnessAdjustmentFlags != 0) {
float lastBrightness = mLastBrightnessEvent.getBrightness();
mTempBrightnessEvent.setInitialBrightness(lastBrightness);
- mTempBrightnessEvent.setAutomaticBrightnessEnabled(mPowerRequest.useAutoBrightness);
+ mTempBrightnessEvent.setAutomaticBrightnessEnabled(mUseAutoBrightness);
mLastBrightnessEvent.copyFrom(mTempBrightnessEvent);
BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent);
// Adjustment flags (and user-set flag) only get added after the equality checks since
@@ -2341,6 +2348,18 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
sendUpdatePowerState();
}
+ private void handleBrightnessModeChange() {
+ final int screenBrightnessModeSetting = Settings.System.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
+ mHandler.post(() -> {
+ mUseAutoBrightness = screenBrightnessModeSetting
+ == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
+ updatePowerState();
+ });
+ }
+
private float getAutoBrightnessAdjustmentSetting() {
final float adj = Settings.System.getFloatForUser(mContext.getContentResolver(),
Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, UserHandle.USER_CURRENT);
@@ -2425,7 +2444,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
boolean hadUserDataPoint) {
final float brightnessInNits = convertToNits(brightness);
- if (mPowerRequest.useAutoBrightness && brightnessInNits >= 0.0f
+ if (mUseAutoBrightness && brightnessInNits >= 0.0f
&& mAutomaticBrightnessController != null && mBrightnessTracker != null) {
// We only want to track changes on devices that can actually map the display backlight
// values into a physical brightness unit since the value provided by the API is in
@@ -2897,7 +2916,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
@Override
public void onChange(boolean selfChange, Uri uri) {
- handleSettingsChange(false /* userSwitch */);
+ if (uri.equals(Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE))) {
+ handleBrightnessModeChange();
+ } else {
+ handleSettingsChange(false /* userSwitch */);
+ }
}
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 346b340edcd1..111caefa34da 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -71,6 +71,7 @@ import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.brightness.DisplayBrightnessController;
import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
+import com.android.server.display.state.DisplayStateController;
import com.android.server.display.utils.SensorUtils;
import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
import com.android.server.display.whitebalance.DisplayWhiteBalanceFactory;
@@ -346,6 +347,9 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
// Tracks and manages the proximity state of the associated display.
private final DisplayPowerProximityStateController mDisplayPowerProximityStateController;
+ // Tracks and manages the display state of the associated display.
+ private final DisplayStateController mDisplayStateController;
+
// A record of state for skipping brightness ramps.
private int mSkipRampState = RAMP_STATE_SKIP_NONE;
@@ -396,6 +400,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
// PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary adjustment set.
private float mTemporaryAutoBrightnessAdjustment;
+ private boolean mUseAutoBrightness;
+
private boolean mIsRbcActive;
// Animators.
@@ -434,6 +440,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
mDisplayPowerProximityStateController = mInjector.getDisplayPowerProximityStateController(
mWakelockController, mDisplayDeviceConfig, mHandler.getLooper(),
() -> updatePowerState(), mDisplayId, mSensorManager);
+ mDisplayStateController = new DisplayStateController(mDisplayPowerProximityStateController);
mTag = "DisplayPowerController2[" + mDisplayId + "]";
mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
@@ -600,6 +607,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
@Override
public void onSwitchUser(@UserIdInt int newUserId) {
handleSettingsChange(true /* userSwitch */);
+ handleBrightnessModeChange();
if (mBrightnessTracker != null) {
mBrightnessTracker.onSwitchUser(newUserId);
}
@@ -842,6 +850,10 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
mContext.getContentResolver().registerContentObserver(
Settings.System.getUriFor(Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ),
false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE),
+ false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
+ handleBrightnessModeChange();
}
private void setUpAutoBrightness(Resources resources, Handler handler) {
@@ -1121,39 +1133,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
mustNotify = !mDisplayReadyLocked;
}
- // Compute the basic display state using the policy.
- // We might override this below based on other factors.
- // Initialise brightness as invalid.
- int state;
- boolean performScreenOffTransition = false;
- switch (mPowerRequest.policy) {
- case DisplayPowerRequest.POLICY_OFF:
- state = Display.STATE_OFF;
- performScreenOffTransition = true;
- break;
- case DisplayPowerRequest.POLICY_DOZE:
- if (mPowerRequest.dozeScreenState != Display.STATE_UNKNOWN) {
- state = mPowerRequest.dozeScreenState;
- } else {
- state = Display.STATE_DOZE;
- }
- break;
- case DisplayPowerRequest.POLICY_DIM:
- case DisplayPowerRequest.POLICY_BRIGHT:
- default:
- state = Display.STATE_ON;
- break;
- }
- assert (state != Display.STATE_UNKNOWN);
-
- mDisplayPowerProximityStateController.updateProximityState(mPowerRequest, state);
-
- if (!mIsEnabled
- || mIsInTransition
- || mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()) {
- state = Display.STATE_OFF;
- }
-
+ int state = mDisplayStateController
+ .updateDisplayState(mPowerRequest, mIsEnabled, mIsInTransition);
// Initialize things the first time the power state is changed.
if (mustInitialize) {
initialize(state);
@@ -1163,7 +1144,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
// The transition may be deferred, so after this point we will use the
// actual state instead of the desired one.
final int oldState = mPowerState.getScreenState();
- animateScreenStateChange(state, performScreenOffTransition);
+ animateScreenStateChange(state, mDisplayStateController.shouldPerformScreenOffTransition());
state = mPowerState.getScreenState();
DisplayBrightnessState displayBrightnessState = mDisplayBrightnessController
@@ -1174,11 +1155,11 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
final boolean autoBrightnessEnabledInDoze =
mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()
&& Display.isDozeState(state);
- final boolean autoBrightnessEnabled = mPowerRequest.useAutoBrightness
+ final boolean autoBrightnessEnabled = mUseAutoBrightness
&& (state == Display.STATE_ON || autoBrightnessEnabledInDoze)
&& Float.isNaN(brightnessState)
&& mAutomaticBrightnessController != null;
- final boolean autoBrightnessDisabledDueToDisplayOff = mPowerRequest.useAutoBrightness
+ final boolean autoBrightnessDisabledDueToDisplayOff = mUseAutoBrightness
&& !(state == Display.STATE_ON || autoBrightnessEnabledInDoze);
final int autoBrightnessState = autoBrightnessEnabled
? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
@@ -1510,7 +1491,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
|| brightnessAdjustmentFlags != 0) {
float lastBrightness = mLastBrightnessEvent.getBrightness();
mTempBrightnessEvent.setInitialBrightness(lastBrightness);
- mTempBrightnessEvent.setAutomaticBrightnessEnabled(mPowerRequest.useAutoBrightness);
+ mTempBrightnessEvent.setAutomaticBrightnessEnabled(mUseAutoBrightness);
mLastBrightnessEvent.copyFrom(mTempBrightnessEvent);
BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent);
// Adjustment flags (and user-set flag) only get added after the equality checks since
@@ -2045,6 +2026,18 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
sendUpdatePowerState();
}
+ private void handleBrightnessModeChange() {
+ final int screenBrightnessModeSetting = Settings.System.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
+ mHandler.post(() -> {
+ mUseAutoBrightness = screenBrightnessModeSetting
+ == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
+ updatePowerState();
+ });
+ }
+
private float getAutoBrightnessAdjustmentSetting() {
final float adj = Settings.System.getFloatForUser(mContext.getContentResolver(),
Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, UserHandle.USER_CURRENT);
@@ -2131,7 +2124,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
boolean hadUserDataPoint) {
final float brightnessInNits = convertToNits(brightness);
- if (mPowerRequest.useAutoBrightness && brightnessInNits >= 0.0f
+ if (mUseAutoBrightness && brightnessInNits >= 0.0f
&& mAutomaticBrightnessController != null && mBrightnessTracker != null) {
// We only want to track changes on devices that can actually map the display backlight
// values into a physical brightness unit since the value provided by the API is in
@@ -2274,10 +2267,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
if (mDisplayBrightnessController != null) {
mDisplayBrightnessController.dump(pw);
}
-
- if (mDisplayPowerProximityStateController != null) {
- mDisplayPowerProximityStateController.dumpLocal(pw);
- }
}
@@ -2499,7 +2488,11 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
@Override
public void onChange(boolean selfChange, Uri uri) {
- handleSettingsChange(false /* userSwitch */);
+ if (uri.equals(Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE))) {
+ handleBrightnessModeChange();
+ } else {
+ handleSettingsChange(false /* userSwitch */);
+ }
}
}
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index c7b27deb420d..ad426b5e00a2 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -66,7 +66,6 @@ import java.util.Objects;
*/
final class LogicalDisplay {
private static final String TAG = "LogicalDisplay";
-
// The layer stack we use when the display has been blanked to prevent any
// of its content from appearing.
private static final int BLANK_LAYER_STACK = -1;
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 80f47a138d08..d7983aecd37b 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -19,6 +19,7 @@ package com.android.server.display;
import static android.view.Display.DEFAULT_DISPLAY;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.hardware.devicestate.DeviceStateManager;
import android.os.Handler;
@@ -29,7 +30,6 @@ import android.os.SystemClock;
import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.SparseArray;
@@ -45,7 +45,6 @@ import com.android.server.display.layout.Layout;
import java.io.PrintWriter;
import java.util.Arrays;
-import java.util.Set;
import java.util.function.Consumer;
/**
@@ -324,58 +323,44 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
}
/**
- * Returns the set of {@link DisplayInfo} for this device state, only fetching the info that is
- * part of the same display group as the provided display id. The DisplayInfo represent the
- * logical display layouts possible for the given device state.
+ * Returns the {@link DisplayInfo} for this device state, indicated by the given display id. The
+ * DisplayInfo represents the attributes of the indicated display in the layout associated with
+ * this state. This is used to get display information for various displays in various states;
+ * e.g. to help apps preload resources for the possible display states.
*
* @param deviceState the state to query possible layouts for
- * @param displayId the display id to apply to all displays within the group
- * @param groupId the display group to filter display info for. Must be the same group as
- * the display with the provided display id.
+ * @param displayId the display id to retrieve
+ * @return {@code null} if no corresponding {@link DisplayInfo} could be found, or the
+ * {@link DisplayInfo} with a matching display id.
*/
- public Set<DisplayInfo> getDisplayInfoForStateLocked(int deviceState, int displayId,
- int groupId) {
- Set<DisplayInfo> displayInfos = new ArraySet<>();
+ @Nullable
+ public DisplayInfo getDisplayInfoForStateLocked(int deviceState, int displayId) {
+ // Retrieve the layout for this particular state.
final Layout layout = mDeviceStateToLayoutMap.get(deviceState);
- final int layoutSize = layout.size();
- for (int i = 0; i < layoutSize; i++) {
- Layout.Display displayLayout = layout.getAt(i);
- if (displayLayout == null) {
- continue;
- }
-
- // If the underlying display-device we want to use for this display
- // doesn't exist, then skip it. This can happen at startup as display-devices
- // trickle in one at a time. When the new display finally shows up, the layout is
- // recalculated so that the display is properly added to the current layout.
- final DisplayAddress address = displayLayout.getAddress();
- final DisplayDevice device = mDisplayDeviceRepo.getByAddressLocked(address);
- if (device == null) {
- Slog.w(TAG, "The display device (" + address + "), is not available"
- + " for the display state " + deviceState);
- continue;
- }
-
- // Find or create the LogicalDisplay to map the DisplayDevice to.
- final int logicalDisplayId = displayLayout.getLogicalDisplayId();
- final LogicalDisplay logicalDisplay = getDisplayLocked(logicalDisplayId);
- if (logicalDisplay == null) {
- Slog.w(TAG, "The logical display (" + address + "), is not available"
- + " for the display state " + deviceState);
- continue;
- }
- final DisplayInfo temp = logicalDisplay.getDisplayInfoLocked();
- DisplayInfo displayInfo = new DisplayInfo(temp);
- if (displayInfo.displayGroupId != groupId) {
- // Ignore any displays not in the provided group.
- continue;
- }
- // A display in the same group can be swapped out at any point, so set the display id
- // for all results to the provided display id.
- displayInfo.displayId = displayId;
- displayInfos.add(displayInfo);
+ if (layout == null) {
+ return null;
+ }
+ // Retrieve the details of the given display within this layout.
+ Layout.Display display = layout.getById(displayId);
+ if (display == null) {
+ return null;
+ }
+ // Retrieve the display info for the display that matches the display id.
+ final DisplayDevice device = mDisplayDeviceRepo.getByAddressLocked(display.getAddress());
+ if (device == null) {
+ Slog.w(TAG, "The display device (" + display.getAddress() + "), is not available"
+ + " for the display state " + mDeviceState);
+ return null;
+ }
+ LogicalDisplay logicalDisplay = getDisplayLocked(device, /* includeDisabled= */ true);
+ if (logicalDisplay == null) {
+ Slog.w(TAG, "The logical display associated with address (" + display.getAddress()
+ + "), is not available for the display state " + mDeviceState);
+ return null;
}
- return displayInfos;
+ DisplayInfo displayInfo = new DisplayInfo(logicalDisplay.getDisplayInfoLocked());
+ displayInfo.displayId = displayId;
+ return displayInfo;
}
public void dumpLocked(PrintWriter pw) {
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index a118b2fa37c3..7c647cf6f4aa 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -599,6 +599,15 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
handleMediaProjectionStoppedLocked(mAppToken);
}
}
+
+ @Override
+ public void onCapturedContentResize(int width, int height) {
+ // Do nothing when we tell the client that the content is resized - it is up to them
+ // to decide to update the VirtualDisplay and Surface.
+ // We could only update the VirtualDisplay size, anyway (which the client wouldn't
+ // expect), and there will still be letterboxing on the output content since the
+ // Surface and VirtualDisplay would then have different aspect ratios.
+ }
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/display/state/DisplayStateController.java b/services/core/java/com/android/server/display/state/DisplayStateController.java
new file mode 100644
index 000000000000..546478e480e0
--- /dev/null
+++ b/services/core/java/com/android/server/display/state/DisplayStateController.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.state;
+
+import android.hardware.display.DisplayManagerInternal;
+import android.util.IndentingPrintWriter;
+import android.view.Display;
+
+import com.android.server.display.DisplayPowerProximityStateController;
+
+import java.io.PrintWriter;
+
+/**
+ * Maintains the DisplayState of the system.
+ * Internally, this accounts for the proximity changes, and notifying the system
+ * clients about the changes
+ */
+public class DisplayStateController {
+ private DisplayPowerProximityStateController mDisplayPowerProximityStateController;
+ private boolean mPerformScreenOffTransition = false;
+
+ public DisplayStateController(DisplayPowerProximityStateController
+ displayPowerProximityStateController) {
+ this.mDisplayPowerProximityStateController = displayPowerProximityStateController;
+ }
+
+ /**
+ * Updates the DisplayState and notifies the system. Also accounts for the
+ * events being emitted by the proximity sensors
+ *
+ * @param displayPowerRequest The request to update the display state
+ * @param isDisplayEnabled A boolean flag representing if the display is enabled
+ * @param isDisplayInTransition A boolean flag representing if the display is undergoing the
+ * transition phase
+ */
+ public int updateDisplayState(DisplayManagerInternal.DisplayPowerRequest displayPowerRequest,
+ boolean isDisplayEnabled, boolean isDisplayInTransition) {
+ mPerformScreenOffTransition = false;
+ // Compute the basic display state using the policy.
+ // We might override this below based on other factors.
+ // Initialise brightness as invalid.
+ int state;
+ switch (displayPowerRequest.policy) {
+ case DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF:
+ state = Display.STATE_OFF;
+ mPerformScreenOffTransition = true;
+ break;
+ case DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE:
+ if (displayPowerRequest.dozeScreenState != Display.STATE_UNKNOWN) {
+ state = displayPowerRequest.dozeScreenState;
+ } else {
+ state = Display.STATE_DOZE;
+ }
+ break;
+ case DisplayManagerInternal.DisplayPowerRequest.POLICY_DIM:
+ case DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT:
+ default:
+ state = Display.STATE_ON;
+ break;
+ }
+ assert (state != Display.STATE_UNKNOWN);
+
+ mDisplayPowerProximityStateController.updateProximityState(displayPowerRequest, state);
+
+ if (!isDisplayEnabled || isDisplayInTransition
+ || mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()) {
+ state = Display.STATE_OFF;
+ }
+
+ return state;
+ }
+
+ /**
+ * Checks if the screen off transition is to be performed or not.
+ */
+ public boolean shouldPerformScreenOffTransition() {
+ return mPerformScreenOffTransition;
+ }
+
+ /**
+ * Used to dump the state.
+ *
+ * @param pw The PrintWriter used to dump the state.
+ */
+ public void dumpsys(PrintWriter pw) {
+ pw.println();
+ pw.println("DisplayPowerProximityStateController:");
+ pw.println(" mPerformScreenOffTransition:" + mPerformScreenOffTransition);
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+ if (mDisplayPowerProximityStateController != null) {
+ mDisplayPowerProximityStateController.dumpLocal(ipw);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index 298098a572b2..01a564d6816f 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -171,4 +171,19 @@ public abstract class InputManagerInternal {
* {@see Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT}
*/
public abstract void decrementKeyboardBacklight(int deviceId);
+
+ /**
+ * Add a runtime association between the input port and device type. Input ports are expected to
+ * be unique.
+ * @param inputPort The port of the input device.
+ * @param type The type of the device. E.g. "touchNavigation".
+ */
+ public abstract void setTypeAssociation(@NonNull String inputPort, @NonNull String type);
+
+ /**
+ * Removes a runtime association between the input device and type.
+ *
+ * @param inputPort The port of the input device.
+ */
+ public abstract void unsetTypeAssociation(@NonNull String inputPort);
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index c62abf004daa..1809b1821b5e 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -248,6 +248,13 @@ public class InputManagerService extends IInputManager.Stub
@GuardedBy("mAssociationsLock")
private final Map<String, String> mUniqueIdAssociations = new ArrayMap<>();
+ // Stores input ports associated with device types. For example, adding an association
+ // {"123", "touchNavigation"} here would mean that a touch device appearing at port "123" would
+ // enumerate as a "touch navigation" device rather than the default "touchpad as a mouse
+ // pointer" device.
+ @GuardedBy("mAssociationsLock")
+ private final Map<String, String> mDeviceTypeAssociations = new ArrayMap<>();
+
// Guards per-display input properties and properties relating to the mouse pointer.
// Threads can wait on this lock to be notified the next time the display on which the mouse
// pointer is shown has changed.
@@ -1905,6 +1912,23 @@ public class InputManagerService extends IInputManager.Stub
mNative.changeUniqueIdAssociation();
}
+ void setTypeAssociationInternal(@NonNull String inputPort, @NonNull String type) {
+ Objects.requireNonNull(inputPort);
+ Objects.requireNonNull(type);
+ synchronized (mAssociationsLock) {
+ mDeviceTypeAssociations.put(inputPort, type);
+ }
+ mNative.changeTypeAssociation();
+ }
+
+ void unsetTypeAssociationInternal(@NonNull String inputPort) {
+ Objects.requireNonNull(inputPort);
+ synchronized (mAssociationsLock) {
+ mDeviceTypeAssociations.remove(inputPort);
+ }
+ mNative.changeTypeAssociation();
+ }
+
@Override // Binder call
public InputSensorInfo[] getSensorList(int deviceId) {
return mNative.getSensorList(deviceId);
@@ -2221,6 +2245,13 @@ public class InputManagerService extends IInputManager.Stub
pw.println(" uniqueId: " + v);
});
}
+ if (!mDeviceTypeAssociations.isEmpty()) {
+ pw.println("Type Associations:");
+ mDeviceTypeAssociations.forEach((k, v) -> {
+ pw.print(" port: " + k);
+ pw.println(" type: " + v);
+ });
+ }
}
}
@@ -2630,6 +2661,18 @@ public class InputManagerService extends IInputManager.Stub
return flatten(associations);
}
+ // Native callback
+ @SuppressWarnings("unused")
+ @VisibleForTesting
+ String[] getDeviceTypeAssociations() {
+ final Map<String, String> associations;
+ synchronized (mAssociationsLock) {
+ associations = new HashMap<>(mDeviceTypeAssociations);
+ }
+
+ return flatten(associations);
+ }
+
/**
* Gets if an input device could dispatch to the given display".
* @param deviceId The input device id.
@@ -3263,6 +3306,16 @@ public class InputManagerService extends IInputManager.Stub
public void decrementKeyboardBacklight(int deviceId) {
mKeyboardBacklightController.decrementKeyboardBacklight(deviceId);
}
+
+ @Override
+ public void setTypeAssociation(@NonNull String inputPort, @NonNull String type) {
+ setTypeAssociationInternal(inputPort, type);
+ }
+
+ @Override
+ public void unsetTypeAssociation(@NonNull String inputPort) {
+ unsetTypeAssociationInternal(inputPort);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 8781c6e2b934..184bc0e3519d 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -186,6 +186,8 @@ interface NativeInputManagerService {
void changeUniqueIdAssociation();
+ void changeTypeAssociation();
+
void notifyPointerDisplayIdChanged();
void setDisplayEligibilityForPointerCapture(int displayId, boolean enabled);
@@ -400,6 +402,9 @@ interface NativeInputManagerService {
public native void changeUniqueIdAssociation();
@Override
+ public native void changeTypeAssociation();
+
+ @Override
public native void notifyPointerDisplayIdChanged();
@Override
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index ab69de961dda..5f39a523b3ac 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -240,8 +240,7 @@ public class LockSettingsService extends ILockSettings.Stub {
protected final UserManager mUserManager;
private final IStorageManager mStorageManager;
private final IActivityManager mActivityManager;
- @VisibleForTesting
- protected final SyntheticPasswordManager mSpManager;
+ private final SyntheticPasswordManager mSpManager;
private final java.security.KeyStore mJavaKeyStore;
private final RecoverableKeyStoreManager mRecoverableKeyStoreManager;
@@ -946,7 +945,7 @@ public class LockSettingsService extends ILockSettings.Stub {
if (!isSyntheticPasswordBasedCredentialLocked(userId)) {
Slogf.i(TAG, "Creating locksettings state for user %d now that boot "
+ "is complete", userId);
- initializeSyntheticPasswordLocked(userId);
+ initializeSyntheticPassword(userId);
}
}
}
@@ -985,7 +984,7 @@ public class LockSettingsService extends ILockSettings.Stub {
long protectorId = getCurrentLskfBasedProtectorId(userId);
if (protectorId == SyntheticPasswordManager.NULL_PROTECTOR_ID) {
Slogf.i(TAG, "Migrating unsecured user %d to SP-based credential", userId);
- initializeSyntheticPasswordLocked(userId);
+ initializeSyntheticPassword(userId);
} else {
Slogf.i(TAG, "Existing unsecured user %d has a synthetic password; re-encrypting CE " +
"key with it", userId);
@@ -1652,13 +1651,7 @@ public class LockSettingsService extends ILockSettings.Stub {
Objects.requireNonNull(savedCredential);
if (DEBUG) Slog.d(TAG, "setLockCredentialInternal: user=" + userId);
synchronized (mSpManager) {
- if (!isSyntheticPasswordBasedCredentialLocked(userId)) {
- if (!savedCredential.isNone()) {
- throw new IllegalStateException("Saved credential given, but user has no SP");
- }
- // TODO(b/232452368): this case is only needed by unit tests now; remove it.
- initializeSyntheticPasswordLocked(userId);
- } else if (savedCredential.isNone() && isProfileWithUnifiedLock(userId)) {
+ if (savedCredential.isNone() && isProfileWithUnifiedLock(userId)) {
// get credential from keystore when profile has unified lock
try {
//TODO: remove as part of b/80170828
@@ -2322,9 +2315,7 @@ public class LockSettingsService extends ILockSettings.Stub {
return;
}
removeStateForReusedUserIdIfNecessary(userId, userSerialNumber);
- synchronized (mSpManager) {
- initializeSyntheticPasswordLocked(userId);
- }
+ initializeSyntheticPassword(userId);
}
}
@@ -2650,21 +2641,22 @@ public class LockSettingsService extends ILockSettings.Stub {
* until the time when Weaver is guaranteed to be available), or when upgrading from Android 13
* or earlier where users with no LSKF didn't necessarily have an SP.
*/
- @GuardedBy("mSpManager")
@VisibleForTesting
- SyntheticPassword initializeSyntheticPasswordLocked(int userId) {
- Slog.i(TAG, "Initialize SyntheticPassword for user: " + userId);
- Preconditions.checkState(getCurrentLskfBasedProtectorId(userId) ==
- SyntheticPasswordManager.NULL_PROTECTOR_ID,
- "Cannot reinitialize SP");
-
- final SyntheticPassword sp = mSpManager.newSyntheticPassword(userId);
- final long protectorId = mSpManager.createLskfBasedProtector(getGateKeeperService(),
- LockscreenCredential.createNone(), sp, userId);
- setCurrentLskfBasedProtectorId(protectorId, userId);
- setUserKeyProtection(userId, sp.deriveFileBasedEncryptionKey());
- onSyntheticPasswordKnown(userId, sp);
- return sp;
+ SyntheticPassword initializeSyntheticPassword(int userId) {
+ synchronized (mSpManager) {
+ Slog.i(TAG, "Initialize SyntheticPassword for user: " + userId);
+ Preconditions.checkState(getCurrentLskfBasedProtectorId(userId) ==
+ SyntheticPasswordManager.NULL_PROTECTOR_ID,
+ "Cannot reinitialize SP");
+
+ final SyntheticPassword sp = mSpManager.newSyntheticPassword(userId);
+ final long protectorId = mSpManager.createLskfBasedProtector(getGateKeeperService(),
+ LockscreenCredential.createNone(), sp, userId);
+ setCurrentLskfBasedProtectorId(protectorId, userId);
+ setUserKeyProtection(userId, sp.deriveFileBasedEncryptionKey());
+ onSyntheticPasswordKnown(userId, sp);
+ return sp;
+ }
}
@VisibleForTesting
@@ -2680,13 +2672,6 @@ public class LockSettingsService extends ILockSettings.Stub {
setLong(LSKF_LAST_CHANGED_TIME_KEY, System.currentTimeMillis(), userId);
}
- @VisibleForTesting
- boolean isSyntheticPasswordBasedCredential(int userId) {
- synchronized (mSpManager) {
- return isSyntheticPasswordBasedCredentialLocked(userId);
- }
- }
-
private boolean isSyntheticPasswordBasedCredentialLocked(int userId) {
if (userId == USER_FRP) {
final int type = mStorage.readPersistentDataBlock().type;
@@ -2925,19 +2910,14 @@ public class LockSettingsService extends ILockSettings.Stub {
@NonNull EscrowTokenStateChangeCallback callback) {
if (DEBUG) Slog.d(TAG, "addEscrowToken: user=" + userId + ", type=" + type);
synchronized (mSpManager) {
- // If the user has no LSKF, then the token can be activated immediately, after creating
- // the user's SP if it doesn't already exist. Otherwise, the token can't be activated
- // until the SP is unlocked by another protector (normally the LSKF-based one).
+ // If the user has no LSKF, then the token can be activated immediately. Otherwise, the
+ // token can't be activated until the SP is unlocked by another protector (normally the
+ // LSKF-based one).
SyntheticPassword sp = null;
if (!isUserSecure(userId)) {
long protectorId = getCurrentLskfBasedProtectorId(userId);
- if (protectorId == SyntheticPasswordManager.NULL_PROTECTOR_ID) {
- // TODO(b/232452368): this case is only needed by unit tests now; remove it.
- sp = initializeSyntheticPasswordLocked(userId);
- } else {
- sp = mSpManager.unlockLskfBasedProtector(getGateKeeperService(), protectorId,
- LockscreenCredential.createNone(), userId, null).syntheticPassword;
- }
+ sp = mSpManager.unlockLskfBasedProtector(getGateKeeperService(), protectorId,
+ LockscreenCredential.createNone(), userId, null).syntheticPassword;
}
disableEscrowTokenOnNonManagedDevicesIfNeeded(userId);
if (!mSpManager.hasEscrowData(userId)) {
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index ed8d852ad2da..50e1fca13877 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -315,7 +315,7 @@ public final class MediaProjectionManagerService extends SystemService
@Override // Binder call
public boolean isValidMediaProjection(IMediaProjection projection) {
return MediaProjectionManagerService.this.isValidMediaProjection(
- projection.asBinder());
+ projection == null ? null : projection.asBinder());
}
@Override // Binder call
@@ -348,7 +348,26 @@ public final class MediaProjectionManagerService extends SystemService
} finally {
Binder.restoreCallingIdentity(token);
}
+ }
+ @Override // Binder call
+ public void notifyActiveProjectionCapturedContentResized(int width, int height) {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to notify "
+ + "on captured content resize");
+ }
+ if (!isValidMediaProjection(mProjectionGrant)) {
+ return;
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (mProjectionGrant != null && mCallbackDelegate != null) {
+ mCallbackDelegate.dispatchResize(mProjectionGrant, width, height);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override //Binder call
@@ -659,9 +678,11 @@ public final class MediaProjectionManagerService extends SystemService
private static class CallbackDelegate {
private Map<IBinder, IMediaProjectionCallback> mClientCallbacks;
+ // Map from the IBinder token representing the callback, to the callback instance.
+ // Represents the callbacks registered on the client's MediaProjectionManager.
private Map<IBinder, IMediaProjectionWatcherCallback> mWatcherCallbacks;
private Handler mHandler;
- private Object mLock = new Object();
+ private final Object mLock = new Object();
public CallbackDelegate() {
mHandler = new Handler(Looper.getMainLooper(), null, true /*async*/);
@@ -715,6 +736,8 @@ public final class MediaProjectionManagerService extends SystemService
}
synchronized (mLock) {
for (IMediaProjectionCallback callback : mClientCallbacks.values()) {
+ // Notify every callback the client has registered for a particular
+ // MediaProjection instance.
mHandler.post(new ClientStopCallback(callback));
}
@@ -724,6 +747,33 @@ public final class MediaProjectionManagerService extends SystemService
}
}
}
+
+ public void dispatchResize(MediaProjection projection, int width, int height) {
+ if (projection == null) {
+ Slog.e(TAG, "Tried to dispatch stop notification for a null media projection."
+ + " Ignoring!");
+ return;
+ }
+ synchronized (mLock) {
+ // TODO(b/249827847) Currently the service assumes there is only one projection
+ // at once - need to find the callback for the given projection, when there are
+ // multiple sessions.
+ for (IMediaProjectionCallback callback : mClientCallbacks.values()) {
+ mHandler.post(() -> {
+ try {
+ // Notify every callback the client has registered for a particular
+ // MediaProjection instance.
+ callback.onCapturedContentResize(width, height);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to notify media projection has resized to " + width
+ + " x " + height, e);
+ }
+ });
+ }
+ // Do not need to notify watcher callback about resize, since watcher callback
+ // is for passing along if recording is still ongoing or not.
+ }
+ }
}
private static final class WatcherStartCallback implements Runnable {
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
index df95f86d1e07..d4c4c694a6c5 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -17,19 +17,46 @@
package com.android.server.pm;
import android.annotation.NonNull;
+import android.app.usage.UsageEvents;
+import android.app.usage.UsageStatsManagerInternal;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.pm.IBackgroundInstallControlService;
import android.content.pm.IPackageManager;
+import android.content.pm.InstallSourceInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
-import android.os.IBinder;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.AtomicFile;
+import android.util.Slog;
import android.util.SparseArrayMap;
+import android.util.SparseSetArray;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+import com.android.server.ServiceThread;
import com.android.server.SystemService;
+import com.android.server.pm.permission.PermissionManagerServiceInternal;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ListIterator;
+import java.util.Set;
+import java.util.TreeSet;
/**
* @hide
@@ -37,14 +64,30 @@ import com.android.server.SystemService;
public class BackgroundInstallControlService extends SystemService {
private static final String TAG = "BackgroundInstallControlService";
+ private static final String DISK_FILE_NAME = "states";
+ private static final String DISK_DIR_NAME = "bic";
+
+ private static final int MAX_FOREGROUND_TIME_FRAMES_SIZE = 10;
+
+ private static final int MSG_USAGE_EVENT_RECEIVED = 0;
+ private static final int MSG_PACKAGE_ADDED = 1;
+ private static final int MSG_PACKAGE_REMOVED = 2;
+
private final Context mContext;
private final BinderService mBinderService;
private final IPackageManager mIPackageManager;
+ private final PackageManagerInternal mPackageManagerInternal;
+ private final UsageStatsManagerInternal mUsageStatsManagerInternal;
+ private final PermissionManagerServiceInternal mPermissionManager;
+ private final Handler mHandler;
+ private final File mDiskFile;
+
- // User ID -> package name -> time diff
- // The time diff between the last foreground activity installer and
- // the "onPackageAdded" function call.
- private final SparseArrayMap<String, Long> mBackgroundInstalledPackages =
+ private SparseSetArray<String> mBackgroundInstalledPackages = null;
+
+ // User ID -> package name -> set of foreground time frame
+ private final SparseArrayMap<String,
+ TreeSet<ForegroundTimeFrame>> mInstallerForegroundTimeFrames =
new SparseArrayMap<>();
public BackgroundInstallControlService(@NonNull Context context) {
@@ -56,49 +99,385 @@ public class BackgroundInstallControlService extends SystemService {
super(injector.getContext());
mContext = injector.getContext();
mIPackageManager = injector.getIPackageManager();
+ mPackageManagerInternal = injector.getPackageManagerInternal();
+ mPermissionManager = injector.getPermissionManager();
+ mHandler = new EventHandler(injector.getLooper(), this);
+ mDiskFile = injector.getDiskFile();
+ mUsageStatsManagerInternal = injector.getUsageStatsManagerInternal();
+ mUsageStatsManagerInternal.registerListener(
+ (userId, event) ->
+ mHandler.obtainMessage(MSG_USAGE_EVENT_RECEIVED,
+ userId,
+ 0,
+ event).sendToTarget()
+ );
mBinderService = new BinderService(this);
}
private static final class BinderService extends IBackgroundInstallControlService.Stub {
final BackgroundInstallControlService mService;
- BinderService(BackgroundInstallControlService service) {
+ BinderService(BackgroundInstallControlService service) {
mService = service;
}
@Override
public ParceledListSlice<PackageInfo> getBackgroundInstalledPackages(
@PackageManager.PackageInfoFlagsBits long flags, int userId) {
- ParceledListSlice<PackageInfo> packages;
- try {
- packages = mService.mIPackageManager.getInstalledPackages(flags, userId);
- } catch (RemoteException e) {
- throw new IllegalStateException("Package manager not available", e);
+ return mService.getBackgroundInstalledPackages(flags, userId);
+ }
+ }
+
+ @VisibleForTesting
+ ParceledListSlice<PackageInfo> getBackgroundInstalledPackages(
+ @PackageManager.PackageInfoFlagsBits long flags, int userId) {
+ ParceledListSlice<PackageInfo> packages;
+ try {
+ packages = mIPackageManager.getInstalledPackages(flags, userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ initBackgroundInstalledPackages();
+
+ ListIterator<PackageInfo> iter = packages.getList().listIterator();
+ while (iter.hasNext()) {
+ String packageName = iter.next().packageName;
+ if (!mBackgroundInstalledPackages.contains(userId, packageName)) {
+ iter.remove();
}
+ }
+
+ return packages;
+ }
+
+ private static class EventHandler extends Handler {
+ private final BackgroundInstallControlService mService;
- // TODO(b/244216300): to enable the test the usage by BinaryTransparencyService,
- // we currently comment out the actual implementation.
- // The fake implementation is just to filter out the first app of the list.
- // for (int i = 0, size = packages.getList().size(); i < size; i++) {
- // String packageName = packages.getList().get(i).packageName;
- // if (!mBackgroundInstalledPackages.contains(userId, packageName) {
- // packages.getList().remove(i);
- // }
- // }
- if (packages.getList().size() > 0) {
- packages.getList().remove(0);
+ EventHandler(Looper looper, BackgroundInstallControlService service) {
+ super(looper);
+ mService = service;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_USAGE_EVENT_RECEIVED: {
+ mService.handleUsageEvent((UsageEvents.Event) msg.obj, msg.arg1 /* userId */);
+ break;
+ }
+ case MSG_PACKAGE_ADDED: {
+ mService.handlePackageAdd((String) msg.obj, msg.arg1 /* userId */);
+ break;
+ }
+ case MSG_PACKAGE_REMOVED: {
+ mService.handlePackageRemove((String) msg.obj, msg.arg1 /* userId */);
+ break;
+ }
+ default:
+ Slog.w(TAG, "Unknown message: " + msg.what);
}
- return packages;
}
}
- /**
- * Called when the system service should publish a binder service using
- * {@link #publishBinderService(String, IBinder).}
- */
+ void handlePackageAdd(String packageName, int userId) {
+ InstallSourceInfo installSourceInfo = null;
+ try {
+ installSourceInfo = mIPackageManager.getInstallSourceInfo(packageName);
+ } catch (RemoteException e) {
+ // Failed to talk to PackageManagerService Should never happen!
+ throw e.rethrowFromSystemServer();
+ }
+ String installerPackageName =
+ installSourceInfo == null ? null : installSourceInfo.getInstallingPackageName();
+ if (installerPackageName == null) {
+ Slog.w(TAG, "fails to get installerPackageName for " + packageName);
+ return;
+ }
+
+ ApplicationInfo appInfo = null;
+ try {
+ appInfo = mIPackageManager.getApplicationInfo(packageName,
+ 0, userId);
+ } catch (RemoteException e) {
+ // Failed to talk to PackageManagerService Should never happen!
+ throw e.rethrowFromSystemServer();
+ }
+
+ if (appInfo == null) {
+ Slog.w(TAG, "fails to get appInfo for " + packageName);
+ return;
+ }
+
+ // convert up-time to current time.
+ final long installTimestamp = System.currentTimeMillis()
+ - (SystemClock.uptimeMillis() - appInfo.createTimestamp);
+
+ if (wasForegroundInstallation(installerPackageName, userId, installTimestamp)) {
+ return;
+ }
+
+ initBackgroundInstalledPackages();
+ mBackgroundInstalledPackages.add(userId, packageName);
+ writeBackgroundInstalledPackagesToDisk();
+ }
+
+ private boolean wasForegroundInstallation(String installerPackageName,
+ int userId, long installTimestamp) {
+ TreeSet<BackgroundInstallControlService.ForegroundTimeFrame> foregroundTimeFrames =
+ mInstallerForegroundTimeFrames.get(userId, installerPackageName);
+
+ // The installer never run in foreground.
+ if (foregroundTimeFrames == null) {
+ return false;
+ }
+
+ for (var foregroundTimeFrame : foregroundTimeFrames) {
+ // the foreground time frame starts later than the installation.
+ // so the installation is outside the foreground time frame.
+ if (foregroundTimeFrame.startTimeStampMillis > installTimestamp) {
+ continue;
+ }
+
+ // the foreground time frame is not over yet.
+ // the installation is inside the foreground time frame.
+ if (!foregroundTimeFrame.isDone()) {
+ return true;
+ }
+
+ // the foreground time frame ends later than the installation.
+ // the installation is inside the foreground time frame.
+ if (installTimestamp <= foregroundTimeFrame.endTimeStampMillis) {
+ return true;
+ }
+ }
+
+ // the installation is not inside any of foreground time frames.
+ // so it is not a foreground installation.
+ return false;
+ }
+
+ void handlePackageRemove(String packageName, int userId) {
+ initBackgroundInstalledPackages();
+ mBackgroundInstalledPackages.remove(userId, packageName);
+ writeBackgroundInstalledPackagesToDisk();
+ }
+
+ void handleUsageEvent(UsageEvents.Event event, int userId) {
+ if (event.mEventType != UsageEvents.Event.ACTIVITY_RESUMED
+ && event.mEventType != UsageEvents.Event.ACTIVITY_PAUSED
+ && event.mEventType != UsageEvents.Event.ACTIVITY_STOPPED) {
+ return;
+ }
+
+ if (!isInstaller(event.mPackage, userId)) {
+ return;
+ }
+
+ if (!mInstallerForegroundTimeFrames.contains(userId, event.mPackage)) {
+ mInstallerForegroundTimeFrames.add(userId, event.mPackage, new TreeSet<>());
+ }
+
+ TreeSet<BackgroundInstallControlService.ForegroundTimeFrame> foregroundTimeFrames =
+ mInstallerForegroundTimeFrames.get(userId, event.mPackage);
+
+ if ((foregroundTimeFrames.size() == 0) || foregroundTimeFrames.last().isDone()) {
+ // ignore the other events if there is no open ForegroundTimeFrame.
+ if (event.mEventType != UsageEvents.Event.ACTIVITY_RESUMED) {
+ return;
+ }
+ foregroundTimeFrames.add(new ForegroundTimeFrame(event.mTimeStamp));
+ }
+
+ foregroundTimeFrames.last().addEvent(event);
+
+ if (foregroundTimeFrames.size() > MAX_FOREGROUND_TIME_FRAMES_SIZE) {
+ foregroundTimeFrames.pollFirst();
+ }
+ }
+
+ @VisibleForTesting
+ void writeBackgroundInstalledPackagesToDisk() {
+ AtomicFile atomicFile = new AtomicFile(mDiskFile);
+ FileOutputStream fileOutputStream;
+ try {
+ fileOutputStream = atomicFile.startWrite();
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to start write to states protobuf.", e);
+ return;
+ }
+
+ try {
+ ProtoOutputStream protoOutputStream = new ProtoOutputStream(fileOutputStream);
+ for (int i = 0; i < mBackgroundInstalledPackages.size(); i++) {
+ int userId = mBackgroundInstalledPackages.keyAt(i);
+ for (String packageName : mBackgroundInstalledPackages.get(userId)) {
+ long token = protoOutputStream.start(
+ BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+ protoOutputStream.write(
+ BackgroundInstalledPackageProto.PACKAGE_NAME, packageName);
+ protoOutputStream.write(
+ BackgroundInstalledPackageProto.USER_ID, userId + 1);
+ protoOutputStream.end(token);
+ }
+ }
+ protoOutputStream.flush();
+ atomicFile.finishWrite(fileOutputStream);
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to finish write to states protobuf.", e);
+ atomicFile.failWrite(fileOutputStream);
+ }
+ }
+
+ @VisibleForTesting
+ void initBackgroundInstalledPackages() {
+ if (mBackgroundInstalledPackages != null) {
+ return;
+ }
+
+ mBackgroundInstalledPackages = new SparseSetArray<>();
+
+ if (!mDiskFile.exists()) {
+ return;
+ }
+
+ AtomicFile atomicFile = new AtomicFile(mDiskFile);
+ try (FileInputStream fileInputStream = atomicFile.openRead()) {
+ ProtoInputStream protoInputStream = new ProtoInputStream(fileInputStream);
+
+ while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ if (protoInputStream.getFieldNumber()
+ != (int) BackgroundInstalledPackagesProto.BG_INSTALLED_PKG) {
+ continue;
+ }
+ long token = protoInputStream.start(
+ BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+ String packageName = null;
+ int userId = UserHandle.USER_NULL;
+ while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (protoInputStream.getFieldNumber()) {
+ case (int) BackgroundInstalledPackageProto.PACKAGE_NAME:
+ packageName = protoInputStream.readString(
+ BackgroundInstalledPackageProto.PACKAGE_NAME);
+ break;
+ case (int) BackgroundInstalledPackageProto.USER_ID:
+ userId = protoInputStream.readInt(
+ BackgroundInstalledPackageProto.USER_ID) - 1;
+ break;
+ default:
+ Slog.w(TAG, "Undefined field in proto: "
+ + protoInputStream.getFieldNumber());
+ }
+ }
+ protoInputStream.end(token);
+ if (packageName != null && userId != UserHandle.USER_NULL) {
+ mBackgroundInstalledPackages.add(userId, packageName);
+ } else {
+ Slog.w(TAG, "Fails to get packageName or UserId from proto file");
+ }
+ }
+ } catch (IOException e) {
+ Slog.w(TAG, "Error reading state from the disk", e);
+ }
+ }
+
+ @VisibleForTesting
+ SparseSetArray<String> getBackgroundInstalledPackages() {
+ return mBackgroundInstalledPackages;
+ }
+
+ @VisibleForTesting
+ SparseArrayMap<String, TreeSet<ForegroundTimeFrame>> getInstallerForegroundTimeFrames() {
+ return mInstallerForegroundTimeFrames;
+ }
+
+ private boolean isInstaller(String pkgName, int userId) {
+ if (mInstallerForegroundTimeFrames.contains(userId, pkgName)) {
+ return true;
+ }
+ return mPermissionManager.checkPermission(pkgName,
+ android.Manifest.permission.INSTALL_PACKAGES,
+ userId) == PackageManager.PERMISSION_GRANTED;
+ }
+
@Override
public void onStart() {
- publishBinderService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE, mBinderService);
+ onStart(/* isForTesting= */ false);
+ }
+
+ @VisibleForTesting
+ void onStart(boolean isForTesting) {
+ if (!isForTesting) {
+ publishBinderService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE, mBinderService);
+ }
+
+ mPackageManagerInternal.getPackageList(new PackageManagerInternal.PackageListObserver() {
+ @Override
+ public void onPackageAdded(String packageName, int uid) {
+ final int userId = UserHandle.getUserId(uid);
+ mHandler.obtainMessage(MSG_PACKAGE_ADDED,
+ userId, 0, packageName).sendToTarget();
+ }
+
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ final int userId = UserHandle.getUserId(uid);
+ mHandler.obtainMessage(MSG_PACKAGE_REMOVED,
+ userId, 0, packageName).sendToTarget();
+ }
+ });
+ }
+
+ // The foreground time frame (ForegroundTimeFrame) represents the period
+ // when a package's activities continuously occupy the foreground.
+ // Each ForegroundTimeFrame starts with an ACTIVITY_RESUMED event,
+ // and then ends with an ACTIVITY_PAUSED or ACTIVITY_STOPPED event.
+ // The startTimeStampMillis stores the timestamp of the ACTIVITY_RESUMED event.
+ // The endTimeStampMillis stores the timestamp of the ACTIVITY_PAUSED or ACTIVITY_STOPPED event
+ // that wraps up the ForegroundTimeFrame.
+ // The activities are designed to handle the edge case in which a package's one activity
+ // seamlessly replace another activity of the same package. Thus, we count these activities
+ // together as a ForegroundTimeFrame. For this scenario, only when all the activities terminate
+ // shall consider the completion of the ForegroundTimeFrame.
+ static final class ForegroundTimeFrame implements Comparable<ForegroundTimeFrame> {
+ public final long startTimeStampMillis;
+ public long endTimeStampMillis;
+ public final Set<Integer> activities;
+
+ public int compareTo(ForegroundTimeFrame o) {
+ int comp = Long.compare(startTimeStampMillis, o.startTimeStampMillis);
+ if (comp != 0) return comp;
+
+ return Integer.compare(hashCode(), o.hashCode());
+ }
+
+ ForegroundTimeFrame(long startTimeStampMillis) {
+ this.startTimeStampMillis = startTimeStampMillis;
+ endTimeStampMillis = 0;
+ activities = new ArraySet<>();
+ }
+
+ public boolean isDone() {
+ return endTimeStampMillis != 0;
+ }
+
+ public void addEvent(UsageEvents.Event event) {
+ switch (event.mEventType) {
+ case UsageEvents.Event.ACTIVITY_RESUMED:
+ activities.add(event.mInstanceId);
+ break;
+ case UsageEvents.Event.ACTIVITY_PAUSED:
+ case UsageEvents.Event.ACTIVITY_STOPPED:
+ if (activities.contains(event.mInstanceId)) {
+ activities.remove(event.mInstanceId);
+ if (activities.size() == 0) {
+ endTimeStampMillis = event.mTimeStamp;
+ }
+ }
+ break;
+ default:
+ }
+ }
}
/**
@@ -108,6 +487,16 @@ public class BackgroundInstallControlService extends SystemService {
Context getContext();
IPackageManager getIPackageManager();
+
+ PackageManagerInternal getPackageManagerInternal();
+
+ UsageStatsManagerInternal getUsageStatsManagerInternal();
+
+ PermissionManagerServiceInternal getPermissionManager();
+
+ Looper getLooper();
+
+ File getDiskFile();
}
private static final class InjectorImpl implements Injector {
@@ -126,5 +515,36 @@ public class BackgroundInstallControlService extends SystemService {
public IPackageManager getIPackageManager() {
return IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
}
+
+ @Override
+ public PackageManagerInternal getPackageManagerInternal() {
+ return LocalServices.getService(PackageManagerInternal.class);
+ }
+
+ @Override
+ public UsageStatsManagerInternal getUsageStatsManagerInternal() {
+ return LocalServices.getService(UsageStatsManagerInternal.class);
+ }
+
+ @Override
+ public PermissionManagerServiceInternal getPermissionManager() {
+ return LocalServices.getService(PermissionManagerServiceInternal.class);
+ }
+
+ @Override
+ public Looper getLooper() {
+ ServiceThread serviceThread = new ServiceThread(TAG,
+ android.os.Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */);
+ serviceThread.start();
+ return serviceThread.getLooper();
+
+ }
+
+ @Override
+ public File getDiskFile() {
+ File dir = new File(Environment.getDataSystemDirectory(), DISK_DIR_NAME);
+ File file = new File(dir, DISK_FILE_NAME);
+ return file;
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index 60621a0eaaef..ea8428351a81 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -570,8 +570,9 @@ public interface Computer extends PackageDataSnapshot {
@PackageManager.InstallReason
int getInstallReason(@NonNull String packageName, @UserIdInt int userId);
- boolean canPackageQuery(@NonNull String sourcePackageName, @NonNull String targetPackageName,
- @UserIdInt int userId);
+ @NonNull
+ boolean[] canPackageQuery(@NonNull String sourcePackageName,
+ @NonNull String[] targetPackageNames, @UserIdInt int userId);
boolean canForwardTo(@NonNull Intent intent, @Nullable String resolvedType,
@UserIdInt int sourceUserId, @UserIdInt int targetUserId);
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index b8fba51c4d60..06aadd92dd51 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -159,6 +159,7 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
@@ -5337,29 +5338,42 @@ public class ComputerEngine implements Computer {
}
@Override
- public boolean canPackageQuery(@NonNull String sourcePackageName,
- @NonNull String targetPackageName, @UserIdInt int userId) {
- if (!mUserManager.exists(userId)) return false;
+ @NonNull
+ public boolean[] canPackageQuery(@NonNull String sourcePackageName,
+ @NonNull String[] targetPackageNames, @UserIdInt int userId) {
+ final int targetSize = targetPackageNames.length;
+ final boolean[] results = new boolean[targetSize];
+ if (!mUserManager.exists(userId)) {
+ return results;
+ }
final int callingUid = Binder.getCallingUid();
enforceCrossUserPermission(callingUid, userId, false /*requireFullPermission*/,
- false /*checkShell*/, "may package query");
+ false /*checkShell*/, "can package query");
+
final PackageStateInternal sourceSetting = getPackageStateInternal(sourcePackageName);
- final PackageStateInternal targetSetting = getPackageStateInternal(targetPackageName);
- boolean throwException = sourceSetting == null || targetSetting == null;
- if (!throwException) {
- final boolean filterSource =
- shouldFilterApplicationIncludingUninstalled(sourceSetting, callingUid, userId);
- final boolean filterTarget =
- shouldFilterApplicationIncludingUninstalled(targetSetting, callingUid, userId);
- // The caller must have visibility of the both packages
- throwException = filterSource || filterTarget;
+ final PackageStateInternal[] targetSettings = new PackageStateInternal[targetSize];
+ // Throw exception if the caller without the visibility of source package
+ boolean throwException =
+ (sourceSetting == null || shouldFilterApplicationIncludingUninstalled(
+ sourceSetting, callingUid, userId));
+ for (int i = 0; !throwException && i < targetSize; i++) {
+ targetSettings[i] = getPackageStateInternal(targetPackageNames[i]);
+ // Throw exception if the caller without the visibility of target package
+ throwException =
+ (targetSettings[i] == null || shouldFilterApplicationIncludingUninstalled(
+ targetSettings[i], callingUid, userId));
}
if (throwException) {
throw new ParcelableException(new PackageManager.NameNotFoundException("Package(s) "
- + sourcePackageName + " and/or " + targetPackageName + " not found."));
+ + sourcePackageName + " and/or " + Arrays.toString(targetPackageNames)
+ + " not found."));
}
+
final int sourcePackageUid = UserHandle.getUid(userId, sourceSetting.getAppId());
- return !shouldFilterApplication(targetSetting, sourcePackageUid, userId);
+ for (int i = 0; i < targetSize; i++) {
+ results[i] = !shouldFilterApplication(targetSettings[i], sourcePackageUid, userId);
+ }
+ return results;
}
/*
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 0066592de8c4..5661399c2ed7 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -62,6 +62,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
import com.android.server.LocalManagerRegistry;
import com.android.server.art.ArtManagerLocal;
+import com.android.server.art.DexUseManagerLocal;
import com.android.server.art.model.ArtFlags;
import com.android.server.art.model.OptimizeParams;
import com.android.server.art.model.OptimizeResult;
@@ -932,9 +933,23 @@ public final class DexOptHelper {
}
/**
- * Returns {@link ArtManagerLocal} if one is found and should be used for package optimization.
+ * Returns {@link DexUseManagerLocal} if ART Service should be used for package optimization.
*/
- private @Nullable ArtManagerLocal getArtManagerLocal() {
+ public static @Nullable DexUseManagerLocal getDexUseManagerLocal() {
+ if (!"true".equals(SystemProperties.get("dalvik.vm.useartservice", ""))) {
+ return null;
+ }
+ try {
+ return LocalManagerRegistry.getManagerOrThrow(DexUseManagerLocal.class);
+ } catch (ManagerNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Returns {@link ArtManagerLocal} if ART Service should be used for package optimization.
+ */
+ private static @Nullable ArtManagerLocal getArtManagerLocal() {
if (!"true".equals(SystemProperties.get("dalvik.vm.useartservice", ""))) {
return null;
}
diff --git a/services/core/java/com/android/server/pm/IPackageManagerBase.java b/services/core/java/com/android/server/pm/IPackageManagerBase.java
index 5c3890c192dd..38efc104bdf9 100644
--- a/services/core/java/com/android/server/pm/IPackageManagerBase.java
+++ b/services/core/java/com/android/server/pm/IPackageManagerBase.java
@@ -1153,9 +1153,10 @@ public abstract class IPackageManagerBase extends IPackageManager.Stub {
@Override
@Deprecated
- public final boolean canPackageQuery(@NonNull String sourcePackageName,
- @NonNull String targetPackageName, @UserIdInt int userId) {
- return snapshot().canPackageQuery(sourcePackageName, targetPackageName, userId);
+ @NonNull
+ public final boolean[] canPackageQuery(@NonNull String sourcePackageName,
+ @NonNull String[] targetPackageNames, @UserIdInt int userId) {
+ return snapshot().canPackageQuery(sourcePackageName, targetPackageNames, userId);
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index e6e2f7988493..759ec67cfba8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -193,6 +193,7 @@ import com.android.server.ServiceThread;
import com.android.server.SystemConfig;
import com.android.server.Watchdog;
import com.android.server.apphibernation.AppHibernationManagerInternal;
+import com.android.server.art.DexUseManagerLocal;
import com.android.server.compat.CompatChange;
import com.android.server.compat.PlatformCompat;
import com.android.server.pm.Installer.InstallerException;
@@ -212,6 +213,7 @@ import com.android.server.pm.permission.LegacyPermissionManagerService;
import com.android.server.pm.permission.PermissionManagerService;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageUserState;
import com.android.server.pm.pkg.PackageUserStateInternal;
@@ -3266,6 +3268,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
return isPackageDeviceAdmin(packageName, UserHandle.USER_ALL);
}
+ // TODO(b/261957226): centralise this logic in DPM
boolean isPackageDeviceAdmin(String packageName, int userId) {
final IDevicePolicyManager dpm = getDevicePolicyManager();
try {
@@ -3292,6 +3295,9 @@ public class PackageManagerService implements PackageSender, TestUtilityService
if (dpm.packageHasActiveAdmins(packageName, users[i])) {
return true;
}
+ if (isDeviceManagementRoleHolder(packageName, users[i])) {
+ return true;
+ }
}
}
} catch (RemoteException e) {
@@ -3299,6 +3305,24 @@ public class PackageManagerService implements PackageSender, TestUtilityService
return false;
}
+ private boolean isDeviceManagementRoleHolder(String packageName, int userId) {
+ return Objects.equals(packageName, getDevicePolicyManagementRoleHolderPackageName(userId));
+ }
+
+ @Nullable
+ private String getDevicePolicyManagementRoleHolderPackageName(int userId) {
+ return Binder.withCleanCallingIdentity(() -> {
+ RoleManager roleManager = mContext.getSystemService(RoleManager.class);
+ List<String> roleHolders =
+ roleManager.getRoleHoldersAsUser(
+ RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT, UserHandle.of(userId));
+ if (roleHolders.isEmpty()) {
+ return null;
+ }
+ return roleHolders.get(0);
+ });
+ }
+
/** Returns the device policy manager interface. */
private IDevicePolicyManager getDevicePolicyManager() {
if (mDevicePolicyManager == null) {
@@ -5308,18 +5332,70 @@ public class PackageManagerService implements PackageSender, TestUtilityService
return;
}
- // TODO(b/254043366): Call `ArtManagerLocal.notifyDexLoad`.
+ UserHandle user = Binder.getCallingUserHandle();
+ int userId = user.getIdentifier();
+
+ // Proxy the call to either ART Service or the legacy implementation. If the
+ // implementation is switched with the system property, the dex usage info will be
+ // incomplete, with these effects:
+ //
+ // - Shared dex files may temporarily get compiled for private use.
+ // - Secondary dex files may not get compiled at all.
+ // - Stale compiled artifacts for secondary dex files may not get cleaned up.
+ //
+ // This recovers in the first background dexopt after the depending apps have been
+ // loaded for the first time.
+
+ DexUseManagerLocal dexUseManager = DexOptHelper.getDexUseManagerLocal();
+ if (dexUseManager != null) {
+ // TODO(chiuwinson): Retrieve filtered snapshot from Computer instance instead.
+ try (PackageManagerLocal.FilteredSnapshot filteredSnapshot =
+ LocalManagerRegistry.getManager(PackageManagerLocal.class)
+ .withFilteredSnapshot(callingUid, user)) {
+ if (loaderIsa != null) {
+ // Check that loaderIsa agrees with the ISA that dexUseManager will
+ // determine.
+ PackageState loadingPkgState =
+ filteredSnapshot.getPackageState(loadingPackageName);
+ // If we don't find the loading package just pass it through and let
+ // dexUseManager throw on it.
+ if (loadingPkgState != null) {
+ String loadingPkgAbi = loadingPkgState.getPrimaryCpuAbi();
+ if (loadingPkgAbi == null) {
+ loadingPkgAbi = Build.SUPPORTED_ABIS[0];
+ }
+ String loadingPkgDexCodeIsa = InstructionSets.getDexCodeInstructionSet(
+ VMRuntime.getInstructionSet(loadingPkgAbi));
+ if (!loaderIsa.equals(loadingPkgDexCodeIsa)) {
+ // TODO(b/251903639): Make this crash to surface this problem
+ // better.
+ Slog.w(PackageManagerService.TAG,
+ "Invalid loaderIsa in notifyDexLoad call from "
+ + loadingPackageName + ", uid " + callingUid
+ + ": expected " + loadingPkgDexCodeIsa + ", got "
+ + loaderIsa);
+ return;
+ }
+ }
+ }
- int userId = UserHandle.getCallingUserId();
- ApplicationInfo ai =
- snapshot.getApplicationInfo(loadingPackageName, /*flags*/ 0, userId);
- if (ai == null) {
- Slog.w(PackageManagerService.TAG, "Loading a package that does not exist for the calling user. package="
- + loadingPackageName + ", user=" + userId);
- return;
+ // This is called from binder, so exceptions thrown here are caught and handled
+ // by it.
+ dexUseManager.notifyDexContainersLoaded(
+ filteredSnapshot, loadingPackageName, classLoaderContextMap);
+ }
+ } else {
+ ApplicationInfo ai =
+ snapshot.getApplicationInfo(loadingPackageName, /*flags*/ 0, userId);
+ if (ai == null) {
+ Slog.w(PackageManagerService.TAG,
+ "Loading a package that does not exist for the calling user. package="
+ + loadingPackageName + ", user=" + userId);
+ return;
+ }
+ mDexManager.notifyDexLoad(ai, classLoaderContextMap, loaderIsa, userId,
+ Process.isIsolated(callingUid));
}
- mDexManager.notifyDexLoad(ai, classLoaderContextMap, loaderIsa, userId,
- Process.isIsolated(callingUid));
}
@Override
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 01dee132a324..7fec0eb00fe4 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -369,8 +369,6 @@ public final class Settings implements Watchable, Snappable {
// Current settings file.
private final File mSettingsFilename;
- // Compressed current settings file.
- private final File mCompressedSettingsFilename;
// Previous settings file.
// Removed when the current settings file successfully stored.
private final File mPreviousSettingsFilename;
@@ -641,7 +639,6 @@ public final class Settings implements Watchable, Snappable {
mRuntimePermissionsPersistence = null;
mPermissionDataProvider = null;
mSettingsFilename = null;
- mCompressedSettingsFilename = null;
mPreviousSettingsFilename = null;
mPackageListFilename = null;
mStoppedPackagesFilename = null;
@@ -713,7 +710,6 @@ public final class Settings implements Watchable, Snappable {
|FileUtils.S_IROTH|FileUtils.S_IXOTH,
-1, -1);
mSettingsFilename = new File(mSystemDir, "packages.xml");
- mCompressedSettingsFilename = new File(mSystemDir, "packages.compressed");
mPreviousSettingsFilename = new File(mSystemDir, "packages-backup.xml");
mPackageListFilename = new File(mSystemDir, "packages.list");
FileUtils.setPermissions(mPackageListFilename, 0640, SYSTEM_UID, PACKAGE_INFO_GID);
@@ -755,7 +751,6 @@ public final class Settings implements Watchable, Snappable {
mLock = null;
mRuntimePermissionsPersistence = r.mRuntimePermissionsPersistence;
mSettingsFilename = null;
- mCompressedSettingsFilename = null;
mPreviousSettingsFilename = null;
mPackageListFilename = null;
mStoppedPackagesFilename = null;
@@ -2597,8 +2592,6 @@ public final class Settings implements Watchable, Snappable {
Slog.w(PackageManagerService.TAG, "Preserving older settings backup");
}
}
- // Compressed settings are not valid anymore.
- mCompressedSettingsFilename.delete();
mPastSignatures.clear();
@@ -2688,30 +2681,10 @@ public final class Settings implements Watchable, Snappable {
mPreviousSettingsFilename.delete();
FileUtils.setPermissions(mSettingsFilename.toString(),
- FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IWGRP,
+ FileUtils.S_IRUSR|FileUtils.S_IWUSR
+ |FileUtils.S_IRGRP|FileUtils.S_IWGRP,
-1, -1);
- final FileInputStream fis = new FileInputStream(mSettingsFilename);
- final AtomicFile compressed = new AtomicFile(mCompressedSettingsFilename);
- final FileOutputStream fos = compressed.startWrite();
-
- BackgroundThread.getHandler().post(() -> {
- try {
- if (!nativeCompressLz4(fis.getFD().getInt$(), fos.getFD().getInt$())) {
- throw new IOException("Failed to compress");
- }
- compressed.finishWrite(fos);
- FileUtils.setPermissions(mCompressedSettingsFilename.toString(),
- FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP
- | FileUtils.S_IWGRP, -1, -1);
- } catch (IOException e) {
- Slog.e(PackageManagerService.TAG, "Failed to write compressed settings file: "
- + mCompressedSettingsFilename, e);
- compressed.delete();
- }
- IoUtils.closeQuietly(fis);
- });
-
writeKernelMappingLPr();
writePackageListLPr();
writeAllUsersPackageRestrictionsLPr(sync);
@@ -2734,8 +2707,6 @@ public final class Settings implements Watchable, Snappable {
//Debug.stopMethodTracing();
}
- private native boolean nativeCompressLz4(int inputFd, int outputFd);
-
private void writeKernelRemoveUserLPr(int userId) {
if (mKernelMappingFilename == null) return;
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 0a650c912b3e..251da8441e0b 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -6986,10 +6986,8 @@ public class UserManagerService extends IUserManager.Stub {
@UserAssignmentResult
public int assignUserToDisplayOnStart(@UserIdInt int userId,
@UserIdInt int profileGroupId, @UserStartMode int userStartMode, int displayId) {
- // TODO(245939659): change UserVisibilityMediator to take @UserStartMode
- boolean foreground = userStartMode == UserManagerInternal.USER_START_MODE_FOREGROUND;
return mUserVisibilityMediator.assignUserToDisplayOnStart(userId, profileGroupId,
- foreground, displayId);
+ userStartMode, displayId);
}
@Override
diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
index 92e8f55287d4..40d87bc33ebd 100644
--- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java
+++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
@@ -23,7 +23,11 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
+import static com.android.server.pm.UserManagerInternal.USER_START_MODE_BACKGROUND;
+import static com.android.server.pm.UserManagerInternal.USER_START_MODE_BACKGROUND_VISIBLE;
+import static com.android.server.pm.UserManagerInternal.USER_START_MODE_FOREGROUND;
import static com.android.server.pm.UserManagerInternal.userAssignmentResultToString;
+import static com.android.server.pm.UserManagerInternal.userStartModeToString;
import android.annotation.IntDef;
import android.annotation.Nullable;
@@ -43,6 +47,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import com.android.server.am.EventLogTags;
import com.android.server.pm.UserManagerInternal.UserAssignmentResult;
+import com.android.server.pm.UserManagerInternal.UserStartMode;
import com.android.server.pm.UserManagerInternal.UserVisibilityListener;
import com.android.server.utils.Slogf;
@@ -142,7 +147,8 @@ public final class UserVisibilityMediator implements Dumpable {
* See {@link UserManagerInternal#assignUserToDisplayOnStart(int, int, int, int)}.
*/
public @UserAssignmentResult int assignUserToDisplayOnStart(@UserIdInt int userId,
- @UserIdInt int unResolvedProfileGroupId, boolean foreground, int displayId) {
+ @UserIdInt int unResolvedProfileGroupId, @UserStartMode int userStartMode,
+ int displayId) {
Preconditions.checkArgument(!isSpecialUserId(userId), "user id cannot be generic: %d",
userId);
// This method needs to perform 4 actions:
@@ -161,14 +167,16 @@ public final class UserVisibilityMediator implements Dumpable {
? userId
: unResolvedProfileGroupId;
if (DBG) {
- Slogf.d(TAG, "assignUserToDisplayOnStart(%d, %d, %b, %d): actualProfileGroupId=%d",
- userId, unResolvedProfileGroupId, foreground, displayId, profileGroupId);
+ Slogf.d(TAG, "assignUserToDisplayOnStart(%d, %d, %s, %d): actualProfileGroupId=%d",
+ userId, unResolvedProfileGroupId, userStartModeToString(userStartMode),
+ displayId, profileGroupId);
}
int result;
IntArray visibleUsersBefore, visibleUsersAfter;
synchronized (mLock) {
- result = getUserVisibilityOnStartLocked(userId, profileGroupId, foreground, displayId);
+ result = getUserVisibilityOnStartLocked(userId, profileGroupId, userStartMode,
+ displayId);
if (DBG) {
Slogf.d(TAG, "result of getUserVisibilityOnStartLocked(%s)",
userAssignmentResultToString(result));
@@ -185,7 +193,7 @@ public final class UserVisibilityMediator implements Dumpable {
visibleUsersBefore = getVisibleUsers();
// Set current user / profiles state
- if (foreground) {
+ if (userStartMode == USER_START_MODE_FOREGROUND) {
mCurrentUserId = userId;
}
if (DBG) {
@@ -228,8 +236,23 @@ public final class UserVisibilityMediator implements Dumpable {
@GuardedBy("mLock")
@UserAssignmentResult
- private int getUserVisibilityOnStartLocked(@UserIdInt int userId,
- @UserIdInt int profileGroupId, boolean foreground, int displayId) {
+ private int getUserVisibilityOnStartLocked(@UserIdInt int userId, @UserIdInt int profileGroupId,
+ @UserStartMode int userStartMode, int displayId) {
+
+ // Check for invalid combinations first
+ if (userStartMode == USER_START_MODE_BACKGROUND && displayId != DEFAULT_DISPLAY) {
+ Slogf.wtf(TAG, "cannot start user (%d) as BACKGROUND_USER on secondary display (%d) "
+ + "(it should be BACKGROUND_USER_VISIBLE", userId, displayId);
+ return USER_ASSIGNMENT_RESULT_FAILURE;
+ }
+ if (userStartMode == USER_START_MODE_BACKGROUND_VISIBLE
+ && displayId == DEFAULT_DISPLAY && !isProfile(userId, profileGroupId)) {
+ Slogf.wtf(TAG, "cannot start full user (%d) visible on default display", userId);
+ return USER_ASSIGNMENT_RESULT_FAILURE;
+ }
+
+ boolean foreground = userStartMode == USER_START_MODE_FOREGROUND;
+
if (displayId != DEFAULT_DISPLAY) {
if (foreground) {
Slogf.w(TAG, "getUserVisibilityOnStartLocked(%d, %d, %b, %d) failed: cannot start "
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index d5fbe46c5582..b5d4afe2cdf2 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2901,7 +2901,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
break;
case KeyEvent.KEYCODE_RECENT_APPS:
- // TODO(b/261621522): Handle recents key presses
+ if (down && repeatCount == 0) {
+ showRecentApps(false /* triggeredFromAltTab */);
+ }
return key_consumed;
case KeyEvent.KEYCODE_APP_SWITCH:
if (!keyguardOn) {
diff --git a/services/core/java/com/android/server/power/PowerGroup.java b/services/core/java/com/android/server/power/PowerGroup.java
index 1c4e143b27e6..522c6c8deb6b 100644
--- a/services/core/java/com/android/server/power/PowerGroup.java
+++ b/services/core/java/com/android/server/power/PowerGroup.java
@@ -407,16 +407,14 @@ public class PowerGroup {
return mDisplayPowerRequest.policy;
}
- boolean updateLocked(float screenBrightnessOverride, boolean autoBrightness,
- boolean useProximitySensor, boolean boostScreenBrightness, int dozeScreenState,
- float dozeScreenBrightness, boolean overrideDrawWakeLock,
- PowerSaveState powerSaverState, boolean quiescent, boolean dozeAfterScreenOff,
- boolean bootCompleted, boolean screenBrightnessBoostInProgress,
- boolean waitForNegativeProximity) {
+ boolean updateLocked(float screenBrightnessOverride, boolean useProximitySensor,
+ boolean boostScreenBrightness, int dozeScreenState, float dozeScreenBrightness,
+ boolean overrideDrawWakeLock, PowerSaveState powerSaverState, boolean quiescent,
+ boolean dozeAfterScreenOff, boolean bootCompleted,
+ boolean screenBrightnessBoostInProgress, boolean waitForNegativeProximity) {
mDisplayPowerRequest.policy = getDesiredScreenPolicyLocked(quiescent, dozeAfterScreenOff,
bootCompleted, screenBrightnessBoostInProgress);
mDisplayPowerRequest.screenBrightnessOverride = screenBrightnessOverride;
- mDisplayPowerRequest.useAutoBrightness = autoBrightness;
mDisplayPowerRequest.useProximitySensor = useProximitySensor;
mDisplayPowerRequest.boostScreenBrightness = boostScreenBrightness;
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 6e3c827e46f0..b5ddc0656282 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -581,10 +581,6 @@ public final class PowerManagerService extends SystemService
private boolean mIsFaceDown = false;
private long mLastFlipTime = 0L;
- // The screen brightness mode.
- // One of the Settings.System.SCREEN_BRIGHTNESS_MODE_* constants.
- private int mScreenBrightnessModeSetting;
-
// The screen brightness setting override from the window manager
// to allow the current foreground activity to override the brightness.
private float mScreenBrightnessOverrideFromWindowManager =
@@ -1457,10 +1453,6 @@ public final class PowerManagerService extends SystemService
mSystemProperties.set(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, retailDemoValue);
}
- mScreenBrightnessModeSetting = Settings.System.getIntForUser(resolver,
- Settings.System.SCREEN_BRIGHTNESS_MODE,
- Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
-
mDirty |= DIRTY_SETTINGS;
}
@@ -3432,23 +3424,18 @@ public final class PowerManagerService extends SystemService
final PowerGroup powerGroup = mPowerGroups.valueAt(idx);
final int groupId = powerGroup.getGroupId();
- // Determine appropriate screen brightness and auto-brightness adjustments.
- final boolean autoBrightness;
+ // Determine appropriate screen brightness.
final float screenBrightnessOverride;
if (!mBootCompleted) {
// Keep the brightness steady during boot. This requires the
// bootloader brightness and the default brightness to be identical.
- autoBrightness = false;
screenBrightnessOverride = mScreenBrightnessDefault;
} else if (isValidBrightness(mScreenBrightnessOverrideFromWindowManager)) {
- autoBrightness = false;
screenBrightnessOverride = mScreenBrightnessOverrideFromWindowManager;
} else {
- autoBrightness = (mScreenBrightnessModeSetting
- == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
screenBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT;
}
- boolean ready = powerGroup.updateLocked(screenBrightnessOverride, autoBrightness,
+ boolean ready = powerGroup.updateLocked(screenBrightnessOverride,
shouldUseProximitySensorLocked(), shouldBoostScreenBrightness(),
mDozeScreenStateOverrideFromDreamManager,
mDozeScreenBrightnessOverrideFromDreamManagerFloat,
@@ -3469,7 +3456,6 @@ public final class PowerManagerService extends SystemService
powerGroup.getUserActivitySummaryLocked())
+ ", mBootCompleted=" + mBootCompleted
+ ", screenBrightnessOverride=" + screenBrightnessOverride
- + ", useAutoBrightness=" + autoBrightness
+ ", mScreenBrightnessBoostInProgress="
+ mScreenBrightnessBoostInProgress
+ ", sQuiescent=" + sQuiescent);
@@ -4488,7 +4474,6 @@ public final class PowerManagerService extends SystemService
+ mMaximumScreenOffTimeoutFromDeviceAdmin + " (enforced="
+ isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked() + ")");
pw.println(" mStayOnWhilePluggedInSetting=" + mStayOnWhilePluggedInSetting);
- pw.println(" mScreenBrightnessModeSetting=" + mScreenBrightnessModeSetting);
pw.println(" mScreenBrightnessOverrideFromWindowManager="
+ mScreenBrightnessOverrideFromWindowManager);
pw.println(" mUserActivityTimeoutOverrideFromWindowManager="
@@ -4866,9 +4851,6 @@ public final class PowerManagerService extends SystemService
proto.end(stayOnWhilePluggedInToken);
proto.write(
- PowerServiceSettingsAndConfigurationDumpProto.SCREEN_BRIGHTNESS_MODE_SETTING,
- mScreenBrightnessModeSetting);
- proto.write(
PowerServiceSettingsAndConfigurationDumpProto
.SCREEN_BRIGHTNESS_OVERRIDE_FROM_WINDOW_MANAGER,
mScreenBrightnessOverrideFromWindowManager);
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index c1920576ad36..721872b73f6a 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -690,7 +690,7 @@ public class TrustManagerService extends SystemService {
*/
public void lockUser(int userId) {
mLockPatternUtils.requireStrongAuth(
- StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST, userId);
+ StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED, userId);
try {
WindowManagerGlobal.getWindowManagerService().lockNow(null);
} catch (RemoteException e) {
@@ -2088,7 +2088,7 @@ public class TrustManagerService extends SystemService {
if (mStrongAuthTracker.isTrustAllowedForUser(mUserId)) {
if (DEBUG) Slog.d(TAG, "Revoking all trust because of trust timeout");
mLockPatternUtils.requireStrongAuth(
- mStrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST, mUserId);
+ mStrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED, mUserId);
}
maybeLockScreen(mUserId);
}
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 05df22f124ed..3be16a1fec44 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -26,6 +26,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED;
+import static android.net.vcn.VcnGatewayConnectionConfig.VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY;
import static android.net.vcn.VcnManager.VCN_ERROR_CODE_CONFIG_ERROR;
import static android.net.vcn.VcnManager.VCN_ERROR_CODE_INTERNAL_ERROR;
import static android.net.vcn.VcnManager.VCN_ERROR_CODE_NETWORK_ERROR;
@@ -36,6 +37,8 @@ import static com.android.server.VcnManagementService.VDBG;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.net.ConnectivityDiagnosticsManager;
+import android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback;
import android.net.ConnectivityManager;
import android.net.InetAddresses;
import android.net.IpPrefix;
@@ -50,6 +53,7 @@ import android.net.NetworkAgent;
import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkProvider;
+import android.net.NetworkRequest;
import android.net.NetworkScore;
import android.net.RouteInfo;
import android.net.TelephonyNetworkSpecifier;
@@ -546,6 +550,39 @@ public class VcnGatewayConnection extends StateMachine {
}
}
+ /**
+ * Sent when there is a suspected data stall on a network
+ *
+ * <p>Only relevant in the Connected state.
+ *
+ * @param arg1 The "all" token; this signal is always honored.
+ * @param obj @NonNull An EventDataStallSuspectedInfo instance with relevant data.
+ */
+ private static final int EVENT_DATA_STALL_SUSPECTED = 13;
+
+ private static class EventDataStallSuspectedInfo implements EventInfo {
+ @NonNull public final Network network;
+
+ EventDataStallSuspectedInfo(@NonNull Network network) {
+ this.network = network;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(network);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object other) {
+ if (!(other instanceof EventDataStallSuspectedInfo)) {
+ return false;
+ }
+
+ final EventDataStallSuspectedInfo rhs = (EventDataStallSuspectedInfo) other;
+ return Objects.equals(network, rhs.network);
+ }
+ }
+
@VisibleForTesting(visibility = Visibility.PRIVATE)
@NonNull
final DisconnectedState mDisconnectedState = new DisconnectedState();
@@ -578,10 +615,13 @@ public class VcnGatewayConnection extends StateMachine {
@NonNull
private final VcnUnderlyingNetworkControllerCallback mUnderlyingNetworkControllerCallback;
+ @NonNull private final VcnConnectivityDiagnosticsCallback mConnectivityDiagnosticsCallback;
+
private final boolean mIsMobileDataEnabled;
@NonNull private final IpSecManager mIpSecManager;
@NonNull private final ConnectivityManager mConnectivityManager;
+ @NonNull private final ConnectivityDiagnosticsManager mConnectivityDiagnosticsManager;
@Nullable private IpSecTunnelInterface mTunnelIface = null;
@@ -748,6 +788,20 @@ public class VcnGatewayConnection extends StateMachine {
mUnderlyingNetworkControllerCallback);
mIpSecManager = mVcnContext.getContext().getSystemService(IpSecManager.class);
mConnectivityManager = mVcnContext.getContext().getSystemService(ConnectivityManager.class);
+ mConnectivityDiagnosticsManager =
+ mVcnContext.getContext().getSystemService(ConnectivityDiagnosticsManager.class);
+
+ mConnectivityDiagnosticsCallback = new VcnConnectivityDiagnosticsCallback();
+
+ if (mConnectionConfig.hasGatewayOption(
+ VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY)) {
+ final NetworkRequest diagRequest =
+ new NetworkRequest.Builder().addTransportType(TRANSPORT_CELLULAR).build();
+ mConnectivityDiagnosticsManager.registerConnectivityDiagnosticsCallback(
+ diagRequest,
+ new HandlerExecutor(new Handler(vcnContext.getLooper())),
+ mConnectivityDiagnosticsCallback);
+ }
addState(mDisconnectedState);
addState(mDisconnectingState);
@@ -810,6 +864,9 @@ public class VcnGatewayConnection extends StateMachine {
mUnderlyingNetworkController.teardown();
mGatewayStatusCallback.onQuit();
+
+ mConnectivityDiagnosticsManager.unregisterConnectivityDiagnosticsCallback(
+ mConnectivityDiagnosticsCallback);
}
/**
@@ -828,6 +885,20 @@ public class VcnGatewayConnection extends StateMachine {
sendMessageAndAcquireWakeLock(EVENT_SUBSCRIPTIONS_CHANGED, TOKEN_ALL);
}
+ private class VcnConnectivityDiagnosticsCallback extends ConnectivityDiagnosticsCallback {
+ @Override
+ public void onDataStallSuspected(ConnectivityDiagnosticsManager.DataStallReport report) {
+ mVcnContext.ensureRunningOnLooperThread();
+
+ final Network network = report.getNetwork();
+ logInfo("Data stall suspected on " + network);
+ sendMessageAndAcquireWakeLock(
+ EVENT_DATA_STALL_SUSPECTED,
+ TOKEN_ALL,
+ new EventDataStallSuspectedInfo(network));
+ }
+ }
+
private class VcnUnderlyingNetworkControllerCallback
implements UnderlyingNetworkControllerCallback {
@Override
@@ -1367,7 +1438,8 @@ public class VcnGatewayConnection extends StateMachine {
case EVENT_SUBSCRIPTIONS_CHANGED: // Fallthrough
case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED: // Fallthrough
case EVENT_MIGRATION_COMPLETED: // Fallthrough
- case EVENT_IKE_CONNECTION_INFO_CHANGED:
+ case EVENT_IKE_CONNECTION_INFO_CHANGED: // Fallthrough
+ case EVENT_DATA_STALL_SUSPECTED:
logUnexpectedEvent(msg.what);
break;
default:
@@ -1925,6 +1997,11 @@ public class VcnGatewayConnection extends StateMachine {
mIkeConnectionInfo =
((EventIkeConnectionInfoChangedInfo) msg.obj).ikeConnectionInfo;
break;
+ case EVENT_DATA_STALL_SUSPECTED:
+ final Network networkWithDataStall =
+ ((EventDataStallSuspectedInfo) msg.obj).network;
+ handleDataStallSuspected(networkWithDataStall);
+ break;
default:
logUnhandledMessage(msg);
break;
@@ -1985,6 +2062,15 @@ public class VcnGatewayConnection extends StateMachine {
}
}
+ private void handleDataStallSuspected(Network networkWithDataStall) {
+ if (mUnderlying != null
+ && mNetworkAgent != null
+ && mNetworkAgent.getNetwork().equals(networkWithDataStall)) {
+ logInfo("Perform Mobility update to recover from suspected data stall");
+ mIkeSession.setNetwork(mUnderlying.network);
+ }
+ }
+
protected void setupInterfaceAndNetworkAgent(
int token,
@NonNull IpSecTunnelInterface tunnelIface,
@@ -2424,6 +2510,11 @@ public class VcnGatewayConnection extends StateMachine {
}
@VisibleForTesting(visibility = Visibility.PRIVATE)
+ ConnectivityDiagnosticsCallback getConnectivityDiagnosticsCallback() {
+ return mConnectivityDiagnosticsCallback;
+ }
+
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
UnderlyingNetworkRecord getUnderlyingNetwork() {
return mUnderlying;
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 4480d521323e..37450acc6f3d 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -603,6 +603,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
* for display.
*/
void generateCrop(WallpaperData wallpaper) {
+ TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
+ t.traceBegin("WPMS.generateCrop");
+ generateCropInternal(wallpaper);
+ t.traceEnd();
+ }
+
+ private void generateCropInternal(WallpaperData wallpaper) {
boolean success = false;
// Only generate crop for default display.
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index b70c8b0d58f4..2b49a81b34bc 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -687,29 +687,32 @@ class ActivityClientController extends IActivityClientController.Stub {
@Override
public int getLaunchedFromUid(IBinder token) {
- if (!canGetLaunchedFrom()) {
- return INVALID_UID;
- }
+ final int uid = Binder.getCallingUid();
+ final boolean isInternalCaller = isInternalCallerGetLaunchedFrom(uid);
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.forTokenLocked(token);
- return r != null ? r.launchedFromUid : INVALID_UID;
+ if (r != null && (isInternalCaller || r.mShareIdentity || r.launchedFromUid == uid)) {
+ return r.launchedFromUid;
+ }
}
+ return INVALID_UID;
}
@Override
public String getLaunchedFromPackage(IBinder token) {
- if (!canGetLaunchedFrom()) {
- return null;
- }
+ final int uid = Binder.getCallingUid();
+ final boolean isInternalCaller = isInternalCallerGetLaunchedFrom(uid);
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.forTokenLocked(token);
- return r != null ? r.launchedFromPackage : null;
+ if (r != null && (isInternalCaller || r.mShareIdentity || r.launchedFromUid == uid)) {
+ return r.launchedFromPackage;
+ }
}
+ return null;
}
- /** Whether the caller can get the package or uid that launched its activity. */
- private boolean canGetLaunchedFrom() {
- final int uid = Binder.getCallingUid();
+ /** Whether the call to one of the getLaunchedFrom APIs is performed by an internal caller. */
+ private boolean isInternalCallerGetLaunchedFrom(int uid) {
if (UserHandle.getAppId(uid) == SYSTEM_UID) {
return true;
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index f725d1a11243..efd0ffab1f60 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -670,7 +670,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
private boolean mCurrentLaunchCanTurnScreenOn = true;
/** Whether our surface was set to be showing in the last call to {@link #prepareSurfaces} */
- private boolean mLastSurfaceShowing = true;
+ private boolean mLastSurfaceShowing;
/**
* The activity is opaque and fills the entire space of this task.
@@ -873,6 +873,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
boolean mEnteringAnimation;
boolean mOverrideTaskTransition;
boolean mDismissKeyguard;
+ boolean mShareIdentity;
/** True if the activity has reported stopped; False if the activity becomes visible. */
boolean mAppStopped;
@@ -1233,8 +1234,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
pw.println(prefix + "supportsEnterPipOnTaskSwitch: "
+ supportsEnterPipOnTaskSwitch);
}
- if (info.getMaxAspectRatio() != 0) {
- pw.println(prefix + "maxAspectRatio=" + info.getMaxAspectRatio());
+ if (getMaxAspectRatio() != 0) {
+ pw.println(prefix + "maxAspectRatio=" + getMaxAspectRatio());
}
final float minAspectRatio = getMinAspectRatio();
if (minAspectRatio != 0) {
@@ -1574,6 +1575,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
newParent.setResumedActivity(this, "onParentChanged");
mImeInsetsFrozenUntilStartInput = false;
}
+ mLetterboxUiController.onActivityParentChanged(newParent);
}
if (rootTask != null && rootTask.topRunningActivity() == this) {
@@ -1997,6 +1999,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mOverrideTaskTransition = options.getOverrideTaskTransition();
mDismissKeyguard = options.getDismissKeyguard();
+ mShareIdentity = options.getShareIdentity();
}
ColorDisplayService.ColorDisplayServiceInternal cds = LocalServices.getService(
@@ -5470,7 +5473,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// no animation but there will still be a transition set.
// We still need to delay hiding the surface such that it
// can be synchronized with showing the next surface in the transition.
- if (!isVisible() && !delayed && !displayContent.mAppTransition.isTransitionSet()) {
+ if (!usingShellTransitions && !isVisible() && !delayed
+ && !displayContent.mAppTransition.isTransitionSet()) {
SurfaceControl.openTransaction();
try {
forAllWindows(win -> {
@@ -7400,6 +7404,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
@Override
+ boolean showSurfaceOnCreation() {
+ return false;
+ }
+
+ @Override
void prepareSurfaces() {
final boolean show = isVisible() || isAnimating(PARENTS,
ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS);
@@ -7638,6 +7647,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
@Configuration.Orientation
@Override
int getRequestedConfigurationOrientation(boolean forDisplay) {
+ if (mLetterboxUiController.hasInheritedOrientation()) {
+ final RootDisplayArea root = getRootDisplayArea();
+ if (forDisplay && root != null && root.isOrientationDifferentFromDisplay()) {
+ return ActivityInfo.reverseOrientation(
+ mLetterboxUiController.getInheritedOrientation());
+ } else {
+ return mLetterboxUiController.getInheritedOrientation();
+ }
+ }
if (mOrientation == SCREEN_ORIENTATION_BEHIND && task != null) {
// We use Task here because we want to be consistent with what happens in
// multi-window mode where other tasks orientations are ignored.
@@ -7765,6 +7783,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
@Nullable
CompatDisplayInsets getCompatDisplayInsets() {
+ if (mLetterboxUiController.hasInheritedLetterboxBehavior()) {
+ return mLetterboxUiController.getInheritedCompatDisplayInsets();
+ }
return mCompatDisplayInsets;
}
@@ -7847,6 +7868,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
private void updateCompatDisplayInsets() {
+ if (mLetterboxUiController.hasInheritedLetterboxBehavior()) {
+ mCompatDisplayInsets = mLetterboxUiController.getInheritedCompatDisplayInsets();
+ return;
+ }
if (mCompatDisplayInsets != null || !shouldCreateCompatDisplayInsets()) {
// The override configuration is set only once in size compatibility mode.
return;
@@ -7908,6 +7933,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
@Override
float getCompatScale() {
+ if (mLetterboxUiController.hasInheritedLetterboxBehavior()) {
+ return mLetterboxUiController.getInheritedSizeCompatScale();
+ }
return hasSizeCompatBounds() ? mSizeCompatScale : super.getCompatScale();
}
@@ -8017,6 +8045,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
/**
+ * @return The orientation to use to understand if reachability is enabled.
+ */
+ @ActivityInfo.ScreenOrientation
+ int getOrientationForReachability() {
+ return mLetterboxUiController.hasInheritedLetterboxBehavior()
+ ? mLetterboxUiController.getInheritedOrientation()
+ : getRequestedConfigurationOrientation();
+ }
+
+ /**
* Returns whether activity bounds are letterboxed.
*
* <p>Note that letterbox UI may not be shown even when this returns {@code true}. See {@link
@@ -8056,6 +8094,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (!ignoreVisibility && !mVisibleRequested) {
return APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE;
}
+ // TODO(b/256564921): Investigate if we need new metrics for translucent activities
+ if (mLetterboxUiController.hasInheritedLetterboxBehavior()) {
+ return mLetterboxUiController.getInheritedAppCompatState();
+ }
if (mInSizeCompatModeForBounds) {
return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE;
}
@@ -8526,6 +8568,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
private boolean isInSizeCompatModeForBounds(final Rect appBounds, final Rect containerBounds) {
+ if (mLetterboxUiController.hasInheritedLetterboxBehavior()) {
+ // To avoid wrong app behaviour, we decided to disable SCM when a translucent activity
+ // is letterboxed.
+ return false;
+ }
final int appWidth = appBounds.width();
final int appHeight = appBounds.height();
final int containerAppWidth = containerBounds.width();
@@ -8546,10 +8593,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// The rest of the condition is that only one side is smaller than the container, but it
// still needs to exclude the cases where the size is limited by the fixed aspect ratio.
- if (info.getMaxAspectRatio() > 0) {
+ final float maxAspectRatio = getMaxAspectRatio();
+ if (maxAspectRatio > 0) {
final float aspectRatio = (0.5f + Math.max(appWidth, appHeight))
/ Math.min(appWidth, appHeight);
- if (aspectRatio >= info.getMaxAspectRatio()) {
+ if (aspectRatio >= maxAspectRatio) {
// The current size has reached the max aspect ratio.
return false;
}
@@ -8771,7 +8819,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
private boolean applyAspectRatio(Rect outBounds, Rect containingAppBounds,
Rect containingBounds, float desiredAspectRatio) {
- final float maxAspectRatio = info.getMaxAspectRatio();
+ final float maxAspectRatio = getMaxAspectRatio();
final Task rootTask = getRootTask();
final float minAspectRatio = getMinAspectRatio();
final TaskFragment organizedTf = getOrganizedTaskFragment();
@@ -8878,6 +8926,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
* Returns the min aspect ratio of this activity.
*/
float getMinAspectRatio() {
+ if (mLetterboxUiController.hasInheritedLetterboxBehavior()) {
+ return mLetterboxUiController.getInheritedMinAspectRatio();
+ }
if (info.applicationInfo == null) {
return info.getMinAspectRatio();
}
@@ -8922,11 +8973,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
&& parent.getWindowConfiguration().getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
}
+ float getMaxAspectRatio() {
+ if (mLetterboxUiController.hasInheritedLetterboxBehavior()) {
+ return mLetterboxUiController.getInheritedMaxAspectRatio();
+ }
+ return info.getMaxAspectRatio();
+ }
+
/**
* Returns true if the activity has maximum or minimum aspect ratio.
*/
private boolean hasFixedAspectRatio() {
- return info.getMaxAspectRatio() != 0 || getMinAspectRatio() != 0;
+ return getMaxAspectRatio() != 0 || getMinAspectRatio() != 0;
}
/**
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 866fef76f3ca..eb04687d7d0c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3616,6 +3616,19 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
@Override
+ public void setSplitScreenResizing(boolean resizing) {
+ enforceTaskPermission("setSplitScreenResizing()");
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ mTaskSupervisor.setSplitScreenResizing(resizing);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
public IWindowOrganizerController getWindowOrganizerController() {
return mWindowOrganizerController;
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 23c2ec71336f..33c90a00234b 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -207,6 +207,9 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
// Used to indicate that a task is removed it should also be removed from recents.
static final boolean REMOVE_FROM_RECENTS = true;
+ /** True if the docked root task is currently being resized. */
+ private boolean mDockedRootTaskResizing;
+
// Activity actions an app cannot start if it uses a permission which is not granted.
private static final ArrayMap<String, String> ACTION_TO_RUNTIME_PERMISSION =
new ArrayMap<>();
@@ -1523,6 +1526,15 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
return mLaunchParamsController;
}
+ void setSplitScreenResizing(boolean resizing) {
+ if (resizing == mDockedRootTaskResizing) {
+ return;
+ }
+
+ mDockedRootTaskResizing = resizing;
+ mWindowManager.setDockedRootTaskResizing(resizing);
+ }
+
private void removePinnedRootTaskInSurfaceTransaction(Task rootTask) {
/**
* Workaround: Force-stop all the activities in the root pinned task before we reparent them
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index 6e23ed966ddc..8d5d0d5c1ce2 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.content.Context.MEDIA_PROJECTION_SERVICE;
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY;
import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
@@ -27,8 +28,10 @@ import android.annotation.Nullable;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
-import android.media.projection.MediaProjectionManager;
+import android.media.projection.IMediaProjectionManager;
import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.provider.DeviceConfig;
import android.view.ContentRecordingSession;
import android.view.Display;
@@ -83,13 +86,7 @@ final class ContentRecorder implements WindowContainerListener {
private int mLastOrientation = ORIENTATION_UNDEFINED;
ContentRecorder(@NonNull DisplayContent displayContent) {
- this(displayContent, () -> {
- MediaProjectionManager mpm = displayContent.mWmService.mContext.getSystemService(
- MediaProjectionManager.class);
- if (mpm != null) {
- mpm.stopActiveProjection();
- }
- });
+ this(displayContent, new RemoteMediaProjectionManagerWrapper());
}
@VisibleForTesting
@@ -445,6 +442,9 @@ final class ContentRecorder implements WindowContainerListener {
.setPosition(mRecordedSurface, shiftedX /* x */, shiftedY /* y */)
.apply();
mLastRecordedBounds = new Rect(recordedContentBounds);
+ // Request to notify the client about the resize.
+ mMediaProjectionManager.notifyActiveProjectionCapturedContentResized(
+ mLastRecordedBounds.width(), mLastRecordedBounds.height());
}
/**
@@ -503,6 +503,56 @@ final class ContentRecorder implements WindowContainerListener {
@VisibleForTesting interface MediaProjectionManagerWrapper {
void stopActiveProjection();
+ void notifyActiveProjectionCapturedContentResized(int width, int height);
+ }
+
+ private static final class RemoteMediaProjectionManagerWrapper implements
+ MediaProjectionManagerWrapper {
+ @Nullable private IMediaProjectionManager mIMediaProjectionManager = null;
+
+ @Override
+ public void stopActiveProjection() {
+ fetchMediaProjectionManager();
+ if (mIMediaProjectionManager == null) {
+ return;
+ }
+ try {
+ mIMediaProjectionManager.stopActiveProjection();
+ } catch (RemoteException e) {
+ ProtoLog.e(WM_DEBUG_CONTENT_RECORDING,
+ "Unable to tell MediaProjectionManagerService to stop the active "
+ + "projection: %s",
+ e);
+ }
+ }
+
+ @Override
+ public void notifyActiveProjectionCapturedContentResized(int width, int height) {
+ fetchMediaProjectionManager();
+ if (mIMediaProjectionManager == null) {
+ return;
+ }
+ try {
+ mIMediaProjectionManager.notifyActiveProjectionCapturedContentResized(width,
+ height);
+ } catch (RemoteException e) {
+ ProtoLog.e(WM_DEBUG_CONTENT_RECORDING,
+ "Unable to tell MediaProjectionManagerService about resizing the active "
+ + "projection: %s",
+ e);
+ }
+ }
+
+ private void fetchMediaProjectionManager() {
+ if (mIMediaProjectionManager != null) {
+ return;
+ }
+ IBinder b = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
+ if (b == null) {
+ return;
+ }
+ mIMediaProjectionManager = IMediaProjectionManager.Stub.asInterface(b);
+ }
}
private boolean isRecordingContentTask() {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 169e7703b1ee..c6dc24f32837 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -525,6 +525,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
/** Remove this display when animation on it has completed. */
private boolean mDeferredRemoval;
+ final DockedTaskDividerController mDividerControllerLocked;
final PinnedTaskController mPinnedTaskController;
final ArrayList<WindowState> mTapExcludedWindows = new ArrayList<>();
@@ -1162,6 +1163,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mDisplayPolicy.systemReady();
}
mWindowCornerRadius = mDisplayPolicy.getWindowCornerRadius();
+ mDividerControllerLocked = new DockedTaskDividerController(this);
mPinnedTaskController = new PinnedTaskController(mWmService, this);
final Transaction pendingTransaction = getPendingTransaction();
@@ -2592,6 +2594,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
}
+ DockedTaskDividerController getDockedDividerController() {
+ return mDividerControllerLocked;
+ }
+
PinnedTaskController getPinnedTaskController() {
return mPinnedTaskController;
}
diff --git a/services/core/java/com/android/server/wm/DockedTaskDividerController.java b/services/core/java/com/android/server/wm/DockedTaskDividerController.java
new file mode 100644
index 000000000000..925a6d858a3d
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DockedTaskDividerController.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.graphics.Rect;
+
+/**
+ * Keeps information about the docked task divider.
+ */
+public class DockedTaskDividerController {
+
+ private final DisplayContent mDisplayContent;
+ private boolean mResizing;
+
+ private final Rect mTouchRegion = new Rect();
+
+ DockedTaskDividerController(DisplayContent displayContent) {
+ mDisplayContent = displayContent;
+ }
+
+ boolean isResizing() {
+ return mResizing;
+ }
+
+ void setResizing(boolean resizing) {
+ if (mResizing != resizing) {
+ mResizing = resizing;
+ resetDragResizingChangeReported();
+ }
+ }
+
+ void setTouchRegion(Rect touchRegion) {
+ mTouchRegion.set(touchRegion);
+ // We need to report touchable region changes to accessibility.
+ if (mDisplayContent.mWmService.mAccessibilityController.hasCallbacks()) {
+ mDisplayContent.mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(
+ mDisplayContent.getDisplayId());
+ }
+ }
+
+ void getTouchRegion(Rect outRegion) {
+ outRegion.set(mTouchRegion);
+ }
+
+ private void resetDragResizingChangeReported() {
+ mDisplayContent.forAllWindows(WindowState::resetDragResizingChangeReported,
+ true /* traverseTopToBottom */);
+ }
+}
diff --git a/services/core/java/com/android/server/wm/DragResizeMode.java b/services/core/java/com/android/server/wm/DragResizeMode.java
new file mode 100644
index 000000000000..684cf06e08b8
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DragResizeMode.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+
+/**
+ * Describes the mode in which a window is drag resizing.
+ */
+class DragResizeMode {
+
+ /**
+ * Freeform mode: Client surface is fullscreen, and client is responsible to draw window at
+ * the correct position.
+ */
+ static final int DRAG_RESIZE_MODE_FREEFORM = 0;
+
+ /**
+ * Mode for resizing the docked (and adjacent) root task: Client surface is fullscreen, but
+ * window is drawn at (0, 0), window manager is responsible for positioning the surface when
+ * dragging.
+ */
+ static final int DRAG_RESIZE_MODE_DOCKED_DIVIDER = 1;
+
+ static boolean isModeAllowedForRootTask(Task rootTask, int mode) {
+ switch (mode) {
+ case DRAG_RESIZE_MODE_FREEFORM:
+ return rootTask.getWindowingMode() == WINDOWING_MODE_FREEFORM;
+ default:
+ return false;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index c19353cb2676..127a7bf1c9a5 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -21,6 +21,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Color;
+import android.provider.DeviceConfig;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
@@ -103,6 +104,10 @@ final class LetterboxConfiguration {
final Context mContext;
+ // Responsible for the persistence of letterbox[Horizontal|Vertical]PositionMultiplier
+ @NonNull
+ private final LetterboxConfigurationPersister mLetterboxConfigurationPersister;
+
// Aspect ratio of letterbox for fixed orientation, values <=
// MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO will be ignored.
private float mFixedOrientationLetterboxAspectRatio;
@@ -165,9 +170,12 @@ final class LetterboxConfiguration {
// Whether using split screen aspect ratio as a default aspect ratio for unresizable apps.
private boolean mIsSplitScreenAspectRatioForUnresizableAppsEnabled;
- // Responsible for the persistence of letterbox[Horizontal|Vertical]PositionMultiplier
- @NonNull
- private final LetterboxConfigurationPersister mLetterboxConfigurationPersister;
+ // Whether letterboxing strategy is enabled for translucent activities. If {@value false}
+ // all the feature is disabled
+ private boolean mTranslucentLetterboxingEnabled;
+
+ // Allows to enable letterboxing strategy for translucent activities ignoring flags.
+ private boolean mTranslucentLetterboxingOverrideEnabled;
LetterboxConfiguration(Context systemUiContext) {
this(systemUiContext, new LetterboxConfigurationPersister(systemUiContext,
@@ -206,6 +214,8 @@ final class LetterboxConfiguration {
R.dimen.config_letterboxDefaultMinAspectRatioForUnresizableApps));
mIsSplitScreenAspectRatioForUnresizableAppsEnabled = mContext.getResources().getBoolean(
R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled);
+ mTranslucentLetterboxingEnabled = mContext.getResources().getBoolean(
+ R.bool.config_letterboxIsEnabledForTranslucentActivities);
mLetterboxConfigurationPersister = letterboxConfigurationPersister;
mLetterboxConfigurationPersister.start();
}
@@ -817,6 +827,32 @@ final class LetterboxConfiguration {
R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled);
}
+ boolean isTranslucentLetterboxingEnabled() {
+ return mTranslucentLetterboxingOverrideEnabled || (mTranslucentLetterboxingEnabled
+ && isTranslucentLetterboxingAllowed());
+ }
+
+ void setTranslucentLetterboxingEnabled(boolean translucentLetterboxingEnabled) {
+ mTranslucentLetterboxingEnabled = translucentLetterboxingEnabled;
+ }
+
+ void setTranslucentLetterboxingOverrideEnabled(
+ boolean translucentLetterboxingOverrideEnabled) {
+ mTranslucentLetterboxingOverrideEnabled = translucentLetterboxingOverrideEnabled;
+ setTranslucentLetterboxingEnabled(translucentLetterboxingOverrideEnabled);
+ }
+
+ /**
+ * Resets whether we use the constraints override strategy for letterboxing when dealing
+ * with translucent activities {@link R.bool.config_letterboxIsEnabledForTranslucentActivities}.
+ */
+ void resetTranslucentLetterboxingEnabled() {
+ final boolean newValue = mContext.getResources().getBoolean(
+ R.bool.config_letterboxIsEnabledForTranslucentActivities);
+ setTranslucentLetterboxingEnabled(newValue);
+ setTranslucentLetterboxingOverrideEnabled(false);
+ }
+
/** Calculates a new letterboxPositionForHorizontalReachability value and updates the store */
private void updatePositionForHorizontalReachability(
Function<Integer, Integer> newHorizonalPositionFun) {
@@ -839,4 +875,9 @@ final class LetterboxConfiguration {
nextVerticalPosition);
}
+ // TODO(b/262378106): Cache runtime flag and implement DeviceConfig.OnPropertiesChangedListener
+ static boolean isTranslucentLetterboxingAllowed() {
+ return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+ "enable_translucent_activity_letterbox", false);
+ }
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index bcea6f4db1dc..a53a5fc00b0c 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -17,6 +17,7 @@
package com.android.server.wm;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
@@ -27,6 +28,7 @@ import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANG
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__RIGHT;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__TOP;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__UNKNOWN_POSITION;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN;
import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__BOTTOM_TO_CENTER;
import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_BOTTOM;
import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_LEFT;
@@ -82,13 +84,44 @@ final class LetterboxUiController {
private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxUiController" : TAG_ATM;
+ private static final float UNDEFINED_ASPECT_RATIO = 0f;
+
private final Point mTmpPoint = new Point();
private final LetterboxConfiguration mLetterboxConfiguration;
+
private final ActivityRecord mActivityRecord;
+ /*
+ * WindowContainerListener responsible to make translucent activities inherit
+ * constraints from the first opaque activity beneath them. It's null for not
+ * translucent activities.
+ */
+ @Nullable
+ private WindowContainerListener mLetterboxConfigListener;
+
private boolean mShowWallpaperForLetterboxBackground;
+ // In case of transparent activities we might need to access the aspectRatio of the
+ // first opaque activity beneath.
+ private float mInheritedMinAspectRatio = UNDEFINED_ASPECT_RATIO;
+ private float mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO;
+
+ @Configuration.Orientation
+ private int mInheritedOrientation = Configuration.ORIENTATION_UNDEFINED;
+
+ // The app compat state for the opaque activity if any
+ private int mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN;
+
+ // If true it means that the opaque activity beneath a translucent one is in SizeCompatMode.
+ private boolean mIsInheritedInSizeCompatMode;
+
+ // This is the SizeCompatScale of the opaque activity beneath a translucent one
+ private float mInheritedSizeCompatScale;
+
+ // The CompatDisplayInsets of the opaque activity beneath the translucent one.
+ private ActivityRecord.CompatDisplayInsets mInheritedCompatDisplayInsets;
+
@Nullable
private Letterbox mLetterbox;
@@ -220,7 +253,9 @@ final class LetterboxUiController {
: mActivityRecord.inMultiWindowMode()
? mActivityRecord.getTask().getBounds()
: mActivityRecord.getRootTask().getParent().getBounds();
- mLetterbox.layout(spaceToFill, w.getFrame(), mTmpPoint);
+ final Rect innerFrame = hasInheritedLetterboxBehavior()
+ ? mActivityRecord.getWindowConfiguration().getBounds() : w.getFrame();
+ mLetterbox.layout(spaceToFill, innerFrame, mTmpPoint);
} else if (mLetterbox != null) {
mLetterbox.hide();
}
@@ -305,7 +340,9 @@ final class LetterboxUiController {
}
private void handleHorizontalDoubleTap(int x) {
- if (!isHorizontalReachabilityEnabled() || mActivityRecord.isInTransition()) {
+ // TODO(b/260857308): Investigate if enabling reachability for translucent activity
+ if (hasInheritedLetterboxBehavior() || !isHorizontalReachabilityEnabled()
+ || mActivityRecord.isInTransition()) {
return;
}
@@ -341,7 +378,9 @@ final class LetterboxUiController {
}
private void handleVerticalDoubleTap(int y) {
- if (!isVerticalReachabilityEnabled() || mActivityRecord.isInTransition()) {
+ // TODO(b/260857308): Investigate if enabling reachability for translucent activity
+ if (hasInheritedLetterboxBehavior() || !isVerticalReachabilityEnabled()
+ || mActivityRecord.isInTransition()) {
return;
}
@@ -390,7 +429,7 @@ final class LetterboxUiController {
&& parentConfiguration.windowConfiguration.getWindowingMode()
== WINDOWING_MODE_FULLSCREEN
&& (parentConfiguration.orientation == ORIENTATION_LANDSCAPE
- && mActivityRecord.getRequestedConfigurationOrientation() == ORIENTATION_PORTRAIT);
+ && mActivityRecord.getOrientationForReachability() == ORIENTATION_PORTRAIT);
}
private boolean isHorizontalReachabilityEnabled() {
@@ -412,7 +451,7 @@ final class LetterboxUiController {
&& parentConfiguration.windowConfiguration.getWindowingMode()
== WINDOWING_MODE_FULLSCREEN
&& (parentConfiguration.orientation == ORIENTATION_PORTRAIT
- && mActivityRecord.getRequestedConfigurationOrientation() == ORIENTATION_LANDSCAPE);
+ && mActivityRecord.getOrientationForReachability() == ORIENTATION_LANDSCAPE);
}
private boolean isVerticalReachabilityEnabled() {
@@ -576,9 +615,7 @@ final class LetterboxUiController {
// Rounded corners should be displayed above the taskbar.
bounds.bottom =
Math.min(bounds.bottom, getTaskbarInsetsSource(mainWindow).getFrame().top);
- if (mActivityRecord.inSizeCompatMode() && mActivityRecord.getCompatScale() < 1.0f) {
- bounds.scale(1.0f / mActivityRecord.getCompatScale());
- }
+ scaleIfNeeded(bounds);
}
private int getInsetsStateCornerRadius(
@@ -788,4 +825,144 @@ final class LetterboxUiController {
w.mAttrs.insetsFlags.appearance
);
}
+
+ /**
+ * Handles translucent activities letterboxing inheriting constraints from the
+ * first opaque activity beneath.
+ * @param parent The parent container.
+ */
+ void onActivityParentChanged(WindowContainer<?> parent) {
+ if (!mLetterboxConfiguration.isTranslucentLetterboxingEnabled()) {
+ return;
+ }
+ if (mLetterboxConfigListener != null) {
+ mLetterboxConfigListener.onRemoved();
+ clearInheritedConfig();
+ }
+ // In case mActivityRecord.getCompatDisplayInsets() is not null we don't apply the
+ // opaque activity constraints because we're expecting the activity is already letterboxed.
+ if (mActivityRecord.getTask() == null || mActivityRecord.getCompatDisplayInsets() != null
+ || mActivityRecord.fillsParent()) {
+ return;
+ }
+ final ActivityRecord firstOpaqueActivityBeneath = mActivityRecord.getTask().getActivity(
+ ActivityRecord::fillsParent, mActivityRecord, false /* includeBoundary */,
+ true /* traverseTopToBottom */);
+ if (firstOpaqueActivityBeneath == null
+ || mActivityRecord.launchedFromUid != firstOpaqueActivityBeneath.getUid()) {
+ // We skip letterboxing if the translucent activity doesn't have any opaque
+ // activities beneath of if it's launched from a different user (e.g. notification)
+ return;
+ }
+ inheritConfiguration(firstOpaqueActivityBeneath);
+ mLetterboxConfigListener = WindowContainer.overrideConfigurationPropagation(
+ mActivityRecord, firstOpaqueActivityBeneath,
+ (opaqueConfig, transparentConfig) -> {
+ final Configuration mutatedConfiguration = new Configuration();
+ final Rect parentBounds = parent.getWindowConfiguration().getBounds();
+ final Rect bounds = mutatedConfiguration.windowConfiguration.getBounds();
+ final Rect letterboxBounds = opaqueConfig.windowConfiguration.getBounds();
+ // We cannot use letterboxBounds directly here because the position relies on
+ // letterboxing. Using letterboxBounds directly, would produce a double offset.
+ bounds.set(parentBounds.left, parentBounds.top,
+ parentBounds.left + letterboxBounds.width(),
+ parentBounds.top + letterboxBounds.height());
+ // We need to initialize appBounds to avoid NPE. The actual value will
+ // be set ahead when resolving the Configuration for the activity.
+ mutatedConfiguration.windowConfiguration.setAppBounds(new Rect());
+ return mutatedConfiguration;
+ });
+ }
+
+ /**
+ * @return {@code true} if the current activity is translucent with an opaque activity
+ * beneath. In this case it will inherit bounds, orientation and aspect ratios from
+ * the first opaque activity beneath.
+ */
+ boolean hasInheritedLetterboxBehavior() {
+ return mLetterboxConfigListener != null && !mActivityRecord.matchParentBounds();
+ }
+
+ /**
+ * @return {@code true} if the current activity is translucent with an opaque activity
+ * beneath and needs to inherit its orientation.
+ */
+ boolean hasInheritedOrientation() {
+ // To force a different orientation, the transparent one needs to have an explicit one
+ // otherwise the existing one is fine and the actual orientation will depend on the
+ // bounds.
+ // To avoid wrong behaviour, we're not forcing orientation for activities with not
+ // fixed orientation (e.g. permission dialogs).
+ return hasInheritedLetterboxBehavior()
+ && mActivityRecord.mOrientation != SCREEN_ORIENTATION_UNSPECIFIED;
+ }
+
+ float getInheritedMinAspectRatio() {
+ return mInheritedMinAspectRatio;
+ }
+
+ float getInheritedMaxAspectRatio() {
+ return mInheritedMaxAspectRatio;
+ }
+
+ int getInheritedAppCompatState() {
+ return mInheritedAppCompatState;
+ }
+
+ float getInheritedSizeCompatScale() {
+ return mInheritedSizeCompatScale;
+ }
+
+ @Configuration.Orientation
+ int getInheritedOrientation() {
+ return mInheritedOrientation;
+ }
+
+ public ActivityRecord.CompatDisplayInsets getInheritedCompatDisplayInsets() {
+ return mInheritedCompatDisplayInsets;
+ }
+
+ private void inheritConfiguration(ActivityRecord firstOpaque) {
+ // To avoid wrong behaviour, we're not forcing a specific aspet ratio to activities
+ // which are not already providing one (e.g. permission dialogs) and presumably also
+ // not resizable.
+ if (mActivityRecord.getMinAspectRatio() != UNDEFINED_ASPECT_RATIO) {
+ mInheritedMinAspectRatio = firstOpaque.getMinAspectRatio();
+ }
+ if (mActivityRecord.getMaxAspectRatio() != UNDEFINED_ASPECT_RATIO) {
+ mInheritedMaxAspectRatio = firstOpaque.getMaxAspectRatio();
+ }
+ mInheritedOrientation = firstOpaque.getRequestedConfigurationOrientation();
+ mInheritedAppCompatState = firstOpaque.getAppCompatState();
+ mIsInheritedInSizeCompatMode = firstOpaque.inSizeCompatMode();
+ mInheritedSizeCompatScale = firstOpaque.getCompatScale();
+ mInheritedCompatDisplayInsets = firstOpaque.getCompatDisplayInsets();
+ }
+
+ private void clearInheritedConfig() {
+ mLetterboxConfigListener = null;
+ mInheritedMinAspectRatio = UNDEFINED_ASPECT_RATIO;
+ mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO;
+ mInheritedOrientation = Configuration.ORIENTATION_UNDEFINED;
+ mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN;
+ mIsInheritedInSizeCompatMode = false;
+ mInheritedSizeCompatScale = 1f;
+ mInheritedCompatDisplayInsets = null;
+ }
+
+ private void scaleIfNeeded(Rect bounds) {
+ if (boundsNeedToScale()) {
+ bounds.scale(1.0f / mActivityRecord.getCompatScale());
+ }
+ }
+
+ private boolean boundsNeedToScale() {
+ if (hasInheritedLetterboxBehavior()) {
+ return mIsInheritedInSizeCompatMode
+ && mInheritedSizeCompatScale < 1.0f;
+ } else {
+ return mActivityRecord.inSizeCompatMode()
+ && mActivityRecord.getCompatScale() < 1.0f;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index 449e77fca399..d395f12f8a01 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -31,6 +31,7 @@ import static com.android.server.wm.ScreenRotationAnimationProto.STARTED;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_SCREEN_ROTATION;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.utils.CoordinateTransforms.computeRotationMatrix;
import android.animation.ArgbEvaluator;
import android.content.Context;
@@ -60,7 +61,6 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.server.display.DisplayControl;
import com.android.server.wm.SurfaceAnimator.AnimationType;
import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
-import com.android.server.wm.utils.RotationAnimationUtils;
import java.io.PrintWriter;
@@ -378,8 +378,7 @@ class ScreenRotationAnimation {
// to the snapshot to make it stay in the same original position
// with the current screen rotation.
int delta = deltaRotation(rotation, mOriginalRotation);
- RotationAnimationUtils.createRotationMatrix(delta, mOriginalWidth, mOriginalHeight,
- mSnapshotInitialMatrix);
+ computeRotationMatrix(delta, mOriginalWidth, mOriginalHeight, mSnapshotInitialMatrix);
setRotationTransform(t, mSnapshotInitialMatrix);
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 7291e27840a9..07e3b836bd1d 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -469,6 +469,7 @@ class Task extends TaskFragment {
// Whether the task is currently being drag-resized
private boolean mDragResizing;
+ private int mDragResizeMode;
// This represents the last resolved activity values for this task
// NOTE: This value needs to be persisted with each task
@@ -2809,6 +2810,11 @@ class Task extends TaskFragment {
}
final Task rootTask = getRootTask();
+ final DisplayContent displayContent = rootTask.getDisplayContent();
+ // It doesn't matter if we in particular are part of the resize, since we couldn't have
+ // a DimLayer anyway if we weren't visible.
+ final boolean dockedResizing = displayContent != null
+ && displayContent.mDividerControllerLocked.isResizing();
if (inFreeformWindowingMode()) {
boolean[] foundTop = { false };
forAllActivities(a -> { getMaxVisibleBounds(a, out, foundTop); });
@@ -2819,10 +2825,18 @@ class Task extends TaskFragment {
if (!matchParentBounds()) {
// When minimizing the root docked task when going home, we don't adjust the task bounds
- // so we need to intersect the task bounds with the root task bounds here..
- rootTask.getBounds(mTmpRect);
- mTmpRect.intersect(getBounds());
- out.set(mTmpRect);
+ // so we need to intersect the task bounds with the root task bounds here.
+ //
+ // If we are Docked Resizing with snap points, the task bounds could be smaller than the
+ // root task bounds and so we don't even want to use them. Even if the app should not be
+ // resized the Dim should keep up with the divider.
+ if (dockedResizing) {
+ rootTask.getBounds(out);
+ } else {
+ rootTask.getBounds(mTmpRect);
+ mTmpRect.intersect(getBounds());
+ out.set(mTmpRect);
+ }
} else {
out.set(getBounds());
}
@@ -2853,15 +2867,16 @@ class Task extends TaskFragment {
}
}
- void setDragResizing(boolean dragResizing) {
+ void setDragResizing(boolean dragResizing, int dragResizeMode) {
if (mDragResizing != dragResizing) {
- // No need to check if allowed if it's leaving dragResize
+ // No need to check if the mode is allowed if it's leaving dragResize
if (dragResizing
- && !(getRootTask().getWindowingMode() == WINDOWING_MODE_FREEFORM)) {
- throw new IllegalArgumentException("Drag resize not allow for root task id="
- + getRootTaskId());
+ && !DragResizeMode.isModeAllowedForRootTask(getRootTask(), dragResizeMode)) {
+ throw new IllegalArgumentException("Drag resize mode not allow for root task id="
+ + getRootTaskId() + " dragResizeMode=" + dragResizeMode);
}
mDragResizing = dragResizing;
+ mDragResizeMode = dragResizeMode;
resetDragResizingChangeReported();
}
}
@@ -2870,6 +2885,10 @@ class Task extends TaskFragment {
return mDragResizing;
}
+ int getDragResizeMode() {
+ return mDragResizeMode;
+ }
+
void adjustBoundsForDisplayChangeIfNeeded(final DisplayContent displayContent) {
if (displayContent == null) {
return;
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 9b3fb6b881c4..5b32149b818d 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -27,6 +27,7 @@ import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE;
import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_RIGHT;
import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_TOP;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -367,7 +368,7 @@ class TaskPositioner implements IBinder.DeathRecipient {
private void endDragLocked() {
mResizing = false;
- mTask.setDragResizing(false);
+ mTask.setDragResizing(false, DRAG_RESIZE_MODE_FREEFORM);
}
/** Returns true if the move operation should be ended. */
@@ -379,7 +380,7 @@ class TaskPositioner implements IBinder.DeathRecipient {
if (mCtrlType != CTRL_NONE) {
resizeDrag(x, y);
- mTask.setDragResizing(true);
+ mTask.setDragResizing(true, DRAG_RESIZE_MODE_FREEFORM);
return false;
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 1d17cd43384a..64574a7e215b 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -642,7 +642,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
if (showSurfaceOnCreation()) {
getSyncTransaction().show(mSurfaceControl);
}
- onSurfaceShown(getSyncTransaction());
updateSurfacePositionNonOrganized();
if (mLastMagnificationSpec != null) {
applyMagnificationSpec(getSyncTransaction(), mLastMagnificationSpec);
@@ -697,13 +696,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
scheduleAnimation();
}
- /**
- * Called when the surface is shown for the first time.
- */
- void onSurfaceShown(Transaction t) {
- // do nothing
- }
-
// Temp. holders for a chain of containers we are currently processing.
private final LinkedList<WindowContainer> mTmpChain1 = new LinkedList<>();
private final LinkedList<WindowContainer> mTmpChain2 = new LinkedList<>();
@@ -3989,27 +3981,54 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
unregisterConfigurationChangeListener(listener);
}
+ static void overrideConfigurationPropagation(WindowContainer<?> receiver,
+ WindowContainer<?> supplier) {
+ overrideConfigurationPropagation(receiver, supplier, null /* configurationMerger */);
+ }
+
/**
* Forces the receiver container to always use the configuration of the supplier container as
* its requested override configuration. It allows to propagate configuration without changing
* the relationship between child and parent.
+ *
+ * @param receiver The {@link WindowContainer<?>} which will receive the {@link
+ * Configuration} result of the merging operation.
+ * @param supplier The {@link WindowContainer<?>} which provides the initial {@link
+ * Configuration}.
+ * @param configurationMerger A {@link ConfigurationMerger} which combines the {@link
+ * Configuration} of the receiver and the supplier.
*/
- static void overrideConfigurationPropagation(WindowContainer<?> receiver,
- WindowContainer<?> supplier) {
+ static WindowContainerListener overrideConfigurationPropagation(WindowContainer<?> receiver,
+ WindowContainer<?> supplier, @Nullable ConfigurationMerger configurationMerger) {
final ConfigurationContainerListener listener = new ConfigurationContainerListener() {
@Override
public void onMergedOverrideConfigurationChanged(Configuration mergedOverrideConfig) {
- receiver.onRequestedOverrideConfigurationChanged(supplier.getConfiguration());
+ final Configuration mergedConfiguration =
+ configurationMerger != null
+ ? configurationMerger.merge(mergedOverrideConfig,
+ receiver.getConfiguration())
+ : supplier.getConfiguration();
+ receiver.onRequestedOverrideConfigurationChanged(mergedConfiguration);
}
};
supplier.registerConfigurationChangeListener(listener);
- receiver.registerWindowContainerListener(new WindowContainerListener() {
+ final WindowContainerListener wcListener = new WindowContainerListener() {
@Override
public void onRemoved() {
receiver.unregisterWindowContainerListener(this);
supplier.unregisterConfigurationChangeListener(listener);
}
- });
+ };
+ receiver.registerWindowContainerListener(wcListener);
+ return wcListener;
+ }
+
+ /**
+ * Abstraction for functions merging two {@link Configuration} objects into one.
+ */
+ @FunctionalInterface
+ interface ConfigurationMerger {
+ Configuration merge(Configuration first, Configuration second);
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 23bce36fc5d4..be1e7e6977fa 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7128,6 +7128,20 @@ public class WindowManagerService extends IWindowManager.Stub
return 0;
}
+ void setDockedRootTaskResizing(boolean resizing) {
+ getDefaultDisplayContentLocked().getDockedDividerController().setResizing(resizing);
+ requestTraversal();
+ }
+
+ @Override
+ public void setDockedTaskDividerTouchRegion(Rect touchRegion) {
+ synchronized (mGlobalLock) {
+ final DisplayContent dc = getDefaultDisplayContentLocked();
+ dc.getDockedDividerController().setTouchRegion(touchRegion);
+ dc.updateTouchExcludeRegion();
+ }
+ }
+
void setForceDesktopModeOnExternalDisplays(boolean forceDesktopModeOnExternalDisplays) {
synchronized (mGlobalLock) {
mForceDesktopModeOnExternalDisplays = forceDesktopModeOnExternalDisplays;
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 46a30fb725de..060784d777f0 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -978,6 +978,29 @@ public class WindowManagerShellCommand extends ShellCommand {
return 0;
}
+ private int runSetTranslucentLetterboxingEnabled(PrintWriter pw) {
+ String arg = getNextArg();
+ final boolean enabled;
+ switch (arg) {
+ case "true":
+ case "1":
+ enabled = true;
+ break;
+ case "false":
+ case "0":
+ enabled = false;
+ break;
+ default:
+ getErrPrintWriter().println("Error: expected true, 1, false, 0, but got " + arg);
+ return -1;
+ }
+
+ synchronized (mInternal.mGlobalLock) {
+ mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(enabled);
+ }
+ return 0;
+ }
+
private int runSetLetterboxStyle(PrintWriter pw) throws RemoteException {
if (peekNextArg() == null) {
getErrPrintWriter().println("Error: No arguments provided.");
@@ -1033,6 +1056,9 @@ public class WindowManagerShellCommand extends ShellCommand {
case "--isSplitScreenAspectRatioForUnresizableAppsEnabled":
runSetLetterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled(pw);
break;
+ case "--isTranslucentLetterboxingEnabled":
+ runSetTranslucentLetterboxingEnabled(pw);
+ break;
default:
getErrPrintWriter().println(
"Error: Unrecognized letterbox style option: " + arg);
@@ -1096,6 +1122,9 @@ public class WindowManagerShellCommand extends ShellCommand {
mLetterboxConfiguration
.getIsSplitScreenAspectRatioForUnresizableAppsEnabled();
break;
+ case "isTranslucentLetterboxingEnabled":
+ mLetterboxConfiguration.resetTranslucentLetterboxingEnabled();
+ break;
default:
getErrPrintWriter().println(
"Error: Unrecognized letterbox style option: " + arg);
@@ -1196,6 +1225,7 @@ public class WindowManagerShellCommand extends ShellCommand {
mLetterboxConfiguration.resetDefaultPositionForVerticalReachability();
mLetterboxConfiguration.resetIsEducationEnabled();
mLetterboxConfiguration.resetIsSplitScreenAspectRatioForUnresizableAppsEnabled();
+ mLetterboxConfiguration.resetTranslucentLetterboxingEnabled();
}
}
@@ -1232,7 +1262,6 @@ public class WindowManagerShellCommand extends ShellCommand {
pw.println("Is using split screen aspect ratio as aspect ratio for unresizable apps: "
+ mLetterboxConfiguration
.getIsSplitScreenAspectRatioForUnresizableAppsEnabled());
-
pw.println("Background type: "
+ LetterboxConfiguration.letterboxBackgroundTypeToString(
mLetterboxConfiguration.getLetterboxBackgroundType()));
@@ -1242,6 +1271,12 @@ public class WindowManagerShellCommand extends ShellCommand {
+ mLetterboxConfiguration.getLetterboxBackgroundWallpaperBlurRadius());
pw.println(" Wallpaper dark scrim alpha: "
+ mLetterboxConfiguration.getLetterboxBackgroundWallpaperDarkScrimAlpha());
+
+ if (mLetterboxConfiguration.isTranslucentLetterboxingEnabled()) {
+ pw.println("Letterboxing for translucent activities: enabled");
+ } else {
+ pw.println("Letterboxing for translucent activities: disabled");
+ }
}
return 0;
}
@@ -1434,12 +1469,16 @@ public class WindowManagerShellCommand extends ShellCommand {
pw.println(" --isSplitScreenAspectRatioForUnresizableAppsEnabled [true|1|false|0]");
pw.println(" Whether using split screen aspect ratio as a default aspect ratio for");
pw.println(" unresizable apps.");
+ pw.println(" --isTranslucentLetterboxingEnabled [true|1|false|0]");
+ pw.println(" Whether letterboxing for translucent activities is enabled.");
+
pw.println(" reset-letterbox-style [aspectRatio|cornerRadius|backgroundType");
pw.println(" |backgroundColor|wallpaperBlurRadius|wallpaperDarkScrimAlpha");
pw.println(" |horizontalPositionMultiplier|verticalPositionMultiplier");
pw.println(" |isHorizontalReachabilityEnabled|isVerticalReachabilityEnabled");
- pw.println(" isEducationEnabled||defaultPositionMultiplierForHorizontalReachability");
- pw.println(" ||defaultPositionMultiplierForVerticalReachability]");
+ pw.println(" |isEducationEnabled||defaultPositionMultiplierForHorizontalReachability");
+ pw.println(" |isTranslucentLetterboxingEnabled");
+ pw.println(" |defaultPositionMultiplierForVerticalReachability]");
pw.println(" Resets overrides to default values for specified properties separated");
pw.println(" by space, e.g. 'reset-letterbox-style aspectRatio cornerRadius'.");
pw.println(" If no arguments provided, all values will be reset.");
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 9b69369d8195..72411727361a 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -51,6 +51,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANI
import static com.android.server.wm.ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED;
import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
+import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
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;
import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
@@ -721,7 +722,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING) != 0) {
- tr.setDragResizing(c.getDragResizing());
+ tr.setDragResizing(c.getDragResizing(), DRAG_RESIZE_MODE_FREEFORM);
}
final int childWindowingMode = c.getActivityWindowingMode();
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 4d6f7bc5011e..1b7bd9e1f36f 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -36,6 +36,9 @@ import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE;
+import static android.view.WindowCallbacks.RESIZE_MODE_DOCKED_DIVIDER;
+import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM;
+import static android.view.WindowCallbacks.RESIZE_MODE_INVALID;
import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowInsets.Type.systemBars;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
@@ -120,6 +123,8 @@ import static com.android.server.policy.WindowManagerPolicy.TRANSIT_PREVIEW_DONE
import static com.android.server.wm.AnimationSpecProto.MOVE;
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
import static com.android.server.wm.DisplayContent.logsGestureExclusionRestrictions;
+import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
+import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
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;
@@ -365,6 +370,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
boolean mHidden = true; // Used to determine if to show child windows.
private boolean mDragResizing;
private boolean mDragResizingChangeReported = true;
+ private int mResizeMode;
private boolean mRedrawForSyncReported;
/**
@@ -3938,14 +3944,24 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
if (isDragResizeChanged) {
setDragResizing();
}
- final boolean isDragResizing = isDragResizing();
+ int resizeMode = RESIZE_MODE_INVALID;
+ if (isDragResizing()) {
+ switch (getResizeMode()) {
+ case DRAG_RESIZE_MODE_FREEFORM:
+ resizeMode = RESIZE_MODE_FREEFORM;
+ break;
+ case DRAG_RESIZE_MODE_DOCKED_DIVIDER:
+ resizeMode = RESIZE_MODE_DOCKED_DIVIDER;
+ break;
+ }
+ }
markRedrawForSyncReported();
try {
mClient.resized(mClientWindowFrames, reportDraw, mLastReportedConfiguration,
getCompatInsetsState(), forceRelayout, alwaysConsumeSystemBars, displayId,
- syncWithBuffers ? mSyncSeqId : -1, isDragResizing);
+ syncWithBuffers ? mSyncSeqId : -1, resizeMode);
if (drawPending && prevRotation >= 0 && prevRotation != mLastReportedConfiguration
.getMergedConfiguration().windowConfiguration.getRotation()) {
mOrientationChangeRedrawRequestTime = SystemClock.elapsedRealtime();
@@ -4188,6 +4204,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
super.resetDragResizingChangeReported();
}
+ int getResizeMode() {
+ return mResizeMode;
+ }
+
private boolean computeDragResizing() {
final Task task = getTask();
if (task == null) {
@@ -4210,7 +4230,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return true;
}
- return false;
+ return getDisplayContent().mDividerControllerLocked.isResizing()
+ && !task.inFreeformWindowingMode() && !isGoneForLayout();
}
void setDragResizing() {
@@ -4219,12 +4240,25 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return;
}
mDragResizing = resizing;
+ final Task task = getTask();
+ if (task != null && task.isDragResizing()) {
+ mResizeMode = task.getDragResizeMode();
+ } else {
+ mResizeMode = mDragResizing && getDisplayContent().mDividerControllerLocked.isResizing()
+ ? DRAG_RESIZE_MODE_DOCKED_DIVIDER
+ : DRAG_RESIZE_MODE_FREEFORM;
+ }
}
boolean isDragResizing() {
return mDragResizing;
}
+ boolean isDockedResizing() {
+ return (mDragResizing && getResizeMode() == DRAG_RESIZE_MODE_DOCKED_DIVIDER)
+ || (isChildWindow() && getParentWindow().isDockedResizing());
+ }
+
@CallSuper
@Override
public void dumpDebug(ProtoOutputStream proto, long fieldId,
@@ -5900,6 +5934,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// level. Because the animation runs before display is rotated, task bounds should
// represent the frames in display space coordinates.
outFrame.set(getTask().getBounds());
+ } else if (isDockedResizing()) {
+ // If we are animating while docked resizing, then use the root task bounds as the
+ // animation target (which will be different than the task bounds)
+ outFrame.set(getTask().getParent().getBounds());
} else {
outFrame.set(getParentFrame());
}
diff --git a/services/core/java/com/android/server/wm/utils/CoordinateTransforms.java b/services/core/java/com/android/server/wm/utils/CoordinateTransforms.java
index a2f37a56598d..7dc8a31b6310 100644
--- a/services/core/java/com/android/server/wm/utils/CoordinateTransforms.java
+++ b/services/core/java/com/android/server/wm/utils/CoordinateTransforms.java
@@ -22,11 +22,9 @@ import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
import android.annotation.Dimension;
-import android.annotation.Nullable;
import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.graphics.RectF;
import android.view.DisplayInfo;
+import android.view.Surface;
import android.view.Surface.Rotation;
public class CoordinateTransforms {
@@ -137,19 +135,24 @@ public class CoordinateTransforms {
out.postConcat(tmp);
}
- /**
- * Transforms a rect using a transformation matrix
- *
- * @param transform the transformation to apply to the rect
- * @param inOutRect the rect to transform
- * @param tmp a temporary value, if null the function will allocate its own.
- */
- public static void transformRect(Matrix transform, Rect inOutRect, @Nullable RectF tmp) {
- if (tmp == null) {
- tmp = new RectF();
+ /** Computes the matrix that rotates the original w x h by the rotation delta. */
+ public static void computeRotationMatrix(int rotationDelta, int w, int h, Matrix outMatrix) {
+ switch (rotationDelta) {
+ case Surface.ROTATION_0:
+ outMatrix.reset();
+ break;
+ case Surface.ROTATION_90:
+ outMatrix.setRotate(90);
+ outMatrix.postTranslate(h, 0);
+ break;
+ case Surface.ROTATION_180:
+ outMatrix.setRotate(180);
+ outMatrix.postTranslate(w, h);
+ break;
+ case Surface.ROTATION_270:
+ outMatrix.setRotate(270);
+ outMatrix.postTranslate(0, w);
+ break;
}
- tmp.set(inOutRect);
- transform.mapRect(tmp);
- inOutRect.set((int) tmp.left, (int) tmp.top, (int) tmp.right, (int) tmp.bottom);
}
}
diff --git a/services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java b/services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java
deleted file mode 100644
index c11a6d02eb18..000000000000
--- a/services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.utils;
-
-import static android.hardware.HardwareBuffer.USAGE_PROTECTED_CONTENT;
-
-import android.graphics.Matrix;
-import android.hardware.HardwareBuffer;
-import android.view.Surface;
-
-
-/** Helper functions for the {@link com.android.server.wm.ScreenRotationAnimation} class*/
-public class RotationAnimationUtils {
-
- /**
- * @return whether the hardwareBuffer passed in is marked as protected.
- */
- public static boolean hasProtectedContent(HardwareBuffer hardwareBuffer) {
- return (hardwareBuffer.getUsage() & USAGE_PROTECTED_CONTENT) == USAGE_PROTECTED_CONTENT;
- }
-
- public static void createRotationMatrix(int rotation, int width, int height, Matrix outMatrix) {
- switch (rotation) {
- case Surface.ROTATION_0:
- outMatrix.reset();
- break;
- case Surface.ROTATION_90:
- outMatrix.setRotate(90, 0, 0);
- outMatrix.postTranslate(height, 0);
- break;
- case Surface.ROTATION_180:
- outMatrix.setRotate(180, 0, 0);
- outMatrix.postTranslate(width, height);
- break;
- case Surface.ROTATION_270:
- outMatrix.setRotate(270, 0, 0);
- outMatrix.postTranslate(0, width);
- break;
- }
- }
-}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 07819b9a18fc..e66168815cd0 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -71,7 +71,6 @@ cc_library_static {
"com_android_server_PersistentDataBlockService.cpp",
"com_android_server_am_LowMemDetector.cpp",
"com_android_server_pm_PackageManagerShellCommandDataLoader.cpp",
- "com_android_server_pm_Settings.cpp",
"com_android_server_sensor_SensorService.cpp",
"com_android_server_wm_TaskFpsCallbackController.cpp",
"onload.cpp",
@@ -153,7 +152,6 @@ cc_defaults {
"libpsi",
"libdataloader",
"libincfs",
- "liblz4",
"android.hardware.audio.common@2.0",
"android.media.audio.common.types-V1-ndk",
"android.hardware.broadcastradio@1.0",
@@ -234,26 +232,3 @@ filegroup {
"com_android_server_app_GameManagerService.cpp",
],
}
-
-// Settings JNI library for unit tests.
-cc_library_shared {
- name: "libservices.core.settings.testonly",
- defaults: ["libservices.core-libs"],
-
- cpp_std: "c++2a",
- cflags: [
- "-Wall",
- "-Werror",
- "-Wno-unused-parameter",
- "-Wthread-safety",
- ],
-
- srcs: [
- "com_android_server_pm_Settings.cpp",
- "onload_settings.cpp",
- ],
-
- header_libs: [
- "bionic_libc_platform_headers",
- ],
-}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 145e0885b105..c36c57159279 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -120,6 +120,7 @@ static struct {
jmethodID getExcludedDeviceNames;
jmethodID getInputPortAssociations;
jmethodID getInputUniqueIdAssociations;
+ jmethodID getDeviceTypeAssociations;
jmethodID getKeyRepeatTimeout;
jmethodID getKeyRepeatDelay;
jmethodID getHoverTapTimeout;
@@ -411,6 +412,8 @@ private:
void ensureSpriteControllerLocked();
sp<SurfaceControl> getParentSurfaceForPointers(int displayId);
static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName);
+ std::unordered_map<std::string, std::string> readMapFromInterleavedJavaArray(
+ jmethodID method, const char* methodName);
static inline JNIEnv* jniEnv() { return AndroidRuntime::getJNIEnv(); }
};
@@ -583,21 +586,14 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon
}
env->DeleteLocalRef(portAssociations);
}
- outConfig->uniqueIdAssociations.clear();
- jobjectArray uniqueIdAssociations = jobjectArray(
- env->CallObjectMethod(mServiceObj, gServiceClassInfo.getInputUniqueIdAssociations));
- if (!checkAndClearExceptionFromCallback(env, "getInputUniqueIdAssociations") &&
- uniqueIdAssociations) {
- jsize length = env->GetArrayLength(uniqueIdAssociations);
- for (jsize i = 0; i < length / 2; i++) {
- std::string inputDeviceUniqueId =
- getStringElementFromJavaArray(env, uniqueIdAssociations, 2 * i);
- std::string displayUniqueId =
- getStringElementFromJavaArray(env, uniqueIdAssociations, 2 * i + 1);
- outConfig->uniqueIdAssociations.insert({inputDeviceUniqueId, displayUniqueId});
- }
- env->DeleteLocalRef(uniqueIdAssociations);
- }
+
+ outConfig->uniqueIdAssociations =
+ readMapFromInterleavedJavaArray(gServiceClassInfo.getInputUniqueIdAssociations,
+ "getInputUniqueIdAssociations");
+
+ outConfig->deviceTypeAssociations =
+ readMapFromInterleavedJavaArray(gServiceClassInfo.getDeviceTypeAssociations,
+ "getDeviceTypeAssociations");
jint hoverTapTimeout = env->CallIntMethod(mServiceObj,
gServiceClassInfo.getHoverTapTimeout);
@@ -647,6 +643,23 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon
} // release lock
}
+std::unordered_map<std::string, std::string> NativeInputManager::readMapFromInterleavedJavaArray(
+ jmethodID method, const char* methodName) {
+ JNIEnv* env = jniEnv();
+ jobjectArray javaArray = jobjectArray(env->CallObjectMethod(mServiceObj, method));
+ std::unordered_map<std::string, std::string> map;
+ if (!checkAndClearExceptionFromCallback(env, methodName) && javaArray) {
+ jsize length = env->GetArrayLength(javaArray);
+ for (jsize i = 0; i < length / 2; i++) {
+ std::string key = getStringElementFromJavaArray(env, javaArray, 2 * i);
+ std::string value = getStringElementFromJavaArray(env, javaArray, 2 * i + 1);
+ map.insert({key, value});
+ }
+ }
+ env->DeleteLocalRef(javaArray);
+ return map;
+}
+
std::shared_ptr<PointerControllerInterface> NativeInputManager::obtainPointerController(
int32_t /* deviceId */) {
ATRACE_CALL();
@@ -2237,6 +2250,12 @@ static void nativeChangeUniqueIdAssociation(JNIEnv* env, jobject nativeImplObj)
InputReaderConfiguration::CHANGE_DISPLAY_INFO);
}
+static void nativeChangeTypeAssociation(JNIEnv* env, jobject nativeImplObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+ im->getInputManager()->getReader().requestRefreshConfiguration(
+ InputReaderConfiguration::CHANGE_DEVICE_TYPE);
+}
+
static void nativeSetMotionClassifierEnabled(JNIEnv* env, jobject nativeImplObj, jboolean enabled) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -2425,6 +2444,7 @@ static const JNINativeMethod gInputManagerMethods[] = {
{"canDispatchToDisplay", "(II)Z", (void*)nativeCanDispatchToDisplay},
{"notifyPortAssociationsChanged", "()V", (void*)nativeNotifyPortAssociationsChanged},
{"changeUniqueIdAssociation", "()V", (void*)nativeChangeUniqueIdAssociation},
+ {"changeTypeAssociation", "()V", (void*)nativeChangeTypeAssociation},
{"setDisplayEligibilityForPointerCapture", "(IZ)V",
(void*)nativeSetDisplayEligibilityForPointerCapture},
{"setMotionClassifierEnabled", "(Z)V", (void*)nativeSetMotionClassifierEnabled},
@@ -2546,6 +2566,9 @@ int register_android_server_InputManager(JNIEnv* env) {
GET_METHOD_ID(gServiceClassInfo.getInputUniqueIdAssociations, clazz,
"getInputUniqueIdAssociations", "()[Ljava/lang/String;");
+ GET_METHOD_ID(gServiceClassInfo.getDeviceTypeAssociations, clazz, "getDeviceTypeAssociations",
+ "()[Ljava/lang/String;");
+
GET_METHOD_ID(gServiceClassInfo.getKeyRepeatTimeout, clazz,
"getKeyRepeatTimeout", "()I");
diff --git a/services/core/jni/com_android_server_pm_Settings.cpp b/services/core/jni/com_android_server_pm_Settings.cpp
deleted file mode 100644
index 9633a115d718..000000000000
--- a/services/core/jni/com_android_server_pm_Settings.cpp
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define ATRACE_TAG ATRACE_TAG_ADB
-#define LOG_TAG "Settings-jni"
-#include <android-base/file.h>
-#include <android-base/logging.h>
-#include <android-base/no_destructor.h>
-#include <core_jni_helpers.h>
-#include <lz4frame.h>
-#include <nativehelper/JNIHelp.h>
-
-#include <vector>
-
-namespace android {
-
-namespace {
-
-struct LZ4FCContextDeleter {
- void operator()(LZ4F_cctx* cctx) { LZ4F_freeCompressionContext(cctx); }
-};
-
-static constexpr int LZ4_BUFFER_SIZE = 64 * 1024;
-
-static bool writeToFile(std::vector<char>& outBuffer, int fdOut) {
- if (!android::base::WriteFully(fdOut, outBuffer.data(), outBuffer.size())) {
- PLOG(ERROR) << "Error to write to output file";
- return false;
- }
- outBuffer.clear();
- return true;
-}
-
-static bool compressAndWriteLz4(LZ4F_cctx* context, std::vector<char>& inBuffer,
- std::vector<char>& outBuffer, int fdOut) {
- auto inSize = inBuffer.size();
- if (inSize > 0) {
- auto prvSize = outBuffer.size();
- auto outSize = LZ4F_compressBound(inSize, nullptr);
- outBuffer.resize(prvSize + outSize);
- auto rc = LZ4F_compressUpdate(context, outBuffer.data() + prvSize, outSize, inBuffer.data(),
- inSize, nullptr);
- if (LZ4F_isError(rc)) {
- LOG(ERROR) << "LZ4F_compressUpdate failed: " << LZ4F_getErrorName(rc);
- return false;
- }
- outBuffer.resize(prvSize + rc);
- }
-
- if (outBuffer.size() > LZ4_BUFFER_SIZE) {
- return writeToFile(outBuffer, fdOut);
- }
-
- return true;
-}
-
-static jboolean nativeCompressLz4(JNIEnv* env, jclass klass, jint fdIn, jint fdOut) {
- LZ4F_cctx* cctx;
- if (LZ4F_createCompressionContext(&cctx, LZ4F_VERSION) != 0) {
- LOG(ERROR) << "Failed to initialize LZ4 compression context.";
- return false;
- }
- std::unique_ptr<LZ4F_cctx, LZ4FCContextDeleter> context(cctx);
-
- std::vector<char> inBuffer, outBuffer;
- inBuffer.reserve(LZ4_BUFFER_SIZE);
- outBuffer.reserve(2 * LZ4_BUFFER_SIZE);
-
- LZ4F_preferences_t prefs;
-
- memset(&prefs, 0, sizeof(prefs));
-
- // Set compression parameters.
- prefs.autoFlush = 0;
- prefs.compressionLevel = 0;
- prefs.frameInfo.blockMode = LZ4F_blockLinked;
- prefs.frameInfo.blockSizeID = LZ4F_default;
- prefs.frameInfo.blockChecksumFlag = LZ4F_noBlockChecksum;
- prefs.frameInfo.contentChecksumFlag = LZ4F_contentChecksumEnabled;
- prefs.favorDecSpeed = 0;
-
- struct stat sb;
- if (fstat(fdIn, &sb) == -1) {
- PLOG(ERROR) << "Failed to obtain input file size.";
- return false;
- }
- prefs.frameInfo.contentSize = sb.st_size;
-
- // Write header first.
- outBuffer.resize(LZ4F_HEADER_SIZE_MAX);
- auto rc = LZ4F_compressBegin(context.get(), outBuffer.data(), outBuffer.size(), &prefs);
- if (LZ4F_isError(rc)) {
- LOG(ERROR) << "LZ4F_compressBegin failed: " << LZ4F_getErrorName(rc);
- return false;
- }
- outBuffer.resize(rc);
-
- bool eof = false;
- while (!eof) {
- constexpr auto capacity = LZ4_BUFFER_SIZE;
- inBuffer.resize(capacity);
- auto read = TEMP_FAILURE_RETRY(::read(fdIn, inBuffer.data(), inBuffer.size()));
- if (read < 0) {
- PLOG(ERROR) << "Failed to read from input file.";
- return false;
- }
-
- inBuffer.resize(read);
-
- if (read == 0) {
- eof = true;
- }
-
- if (!compressAndWriteLz4(context.get(), inBuffer, outBuffer, fdOut)) {
- return false;
- }
- }
-
- // Footer.
- auto prvSize = outBuffer.size();
- outBuffer.resize(outBuffer.capacity());
- rc = LZ4F_compressEnd(context.get(), outBuffer.data() + prvSize, outBuffer.size() - prvSize,
- nullptr);
- if (LZ4F_isError(rc)) {
- LOG(ERROR) << "LZ4F_compressEnd failed: " << LZ4F_getErrorName(rc);
- return false;
- }
- outBuffer.resize(prvSize + rc);
-
- if (!writeToFile(outBuffer, fdOut)) {
- return false;
- }
-
- return true;
-}
-
-static const JNINativeMethod method_table[] = {
- {"nativeCompressLz4", "(II)Z", (void*)nativeCompressLz4},
-};
-
-} // namespace
-
-int register_android_server_com_android_server_pm_Settings(JNIEnv* env) {
- return jniRegisterNativeMethods(env, "com/android/server/pm/Settings", method_table,
- NELEM(method_table));
-}
-
-} // namespace android
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 184505713420..00f851f9f4ff 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -56,7 +56,6 @@ int register_android_server_am_LowMemDetector(JNIEnv* env);
int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(JNIEnv* env);
int register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(JNIEnv* env);
int register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(JNIEnv* env);
-int register_android_server_com_android_server_pm_Settings(JNIEnv* env);
int register_android_server_AdbDebuggingManager(JNIEnv* env);
int register_android_server_FaceService(JNIEnv* env);
int register_android_server_GpuService(JNIEnv* env);
@@ -115,7 +114,6 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(env);
register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(env);
register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(env);
- register_android_server_com_android_server_pm_Settings(env);
register_android_server_AdbDebuggingManager(env);
register_android_server_FaceService(env);
register_android_server_GpuService(env);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 509d75bf9b76..c68eea8f35b9 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -109,6 +109,7 @@ import com.android.server.ambientcontext.AmbientContextManagerService;
import com.android.server.appbinding.AppBindingService;
import com.android.server.art.ArtManagerLocal;
import com.android.server.art.ArtModuleServiceInitializer;
+import com.android.server.art.DexUseManagerLocal;
import com.android.server.attention.AttentionManagerService;
import com.android.server.audio.AudioService;
import com.android.server.biometrics.AuthService;
@@ -1226,6 +1227,13 @@ public final class SystemServer implements Dumpable {
Watchdog.getInstance().resumeWatchingCurrentThread("packagemanagermain");
}
+ // DexUseManagerLocal needs to be loaded after PackageManagerLocal has been registered, but
+ // before PackageManagerService starts processing binder calls to notifyDexLoad.
+ // DexUseManagerLocal may also call artd, so ensure ArtModuleServiceManager is instantiated.
+ ArtModuleServiceInitializer.setArtModuleServiceManager(new ArtModuleServiceManager());
+ LocalManagerRegistry.addManager(
+ DexUseManagerLocal.class, DexUseManagerLocal.createInstance());
+
mFirstBoot = mPackageManagerService.isFirstBoot();
mPackageManager = mSystemContext.getPackageManager();
t.traceEnd();
@@ -2729,7 +2737,6 @@ public final class SystemServer implements Dumpable {
t.traceEnd();
t.traceBegin("ArtManagerLocal");
- ArtModuleServiceInitializer.setArtModuleServiceManager(new ArtModuleServiceManager());
LocalManagerRegistry.addManager(ArtManagerLocal.class, new ArtManagerLocal(context));
t.traceEnd();
diff --git a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
index f648be9626b6..f493b8985c51 100644
--- a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
@@ -24,7 +24,7 @@ import com.android.server.SystemConfig
import com.android.server.SystemService
import com.android.server.appop.AppOpsCheckingServiceInterface
import com.android.server.permission.access.appop.AppOpService
-import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.collection.IntSet
import com.android.server.permission.access.permission.PermissionService
import com.android.server.pm.PackageManagerLocal
import com.android.server.pm.UserManagerService
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
index 1b055202af7c..6b2b1856f7fe 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
@@ -16,18 +16,468 @@
package com.android.server.permission.access.permission
+import android.app.AppOpsManager
+import android.app.admin.DevicePolicyManager
+import android.content.pm.PackageManager
+import android.os.Build
+import android.permission.PermissionManager
+import com.android.server.permission.access.util.andInv
+import com.android.server.permission.access.util.hasAnyBit
+import com.android.server.permission.access.util.hasBits
+
+/**
+ * A set of internal permission flags that's better than the set of `FLAG_PERMISSION_*` constants on
+ * [PackageManager].
+ *
+ * The old binary permission state is now tracked by multiple `*_GRANTED` and `*_REVOKED` flags, so
+ * that:
+ *
+ * - With [INSTALL_GRANTED] and [INSTALL_REVOKED], we can now get rid of the old per-package
+ * `areInstallPermissionsFixed` attribute and correctly track it per-permission, finally fixing
+ * edge cases during module rollbacks.
+ *
+ * - With [LEGACY_GRANTED] and [IMPLICIT_GRANTED], we can now ensure that legacy permissions and
+ * implicit permissions split from non-runtime permissions are never revoked, without checking
+ * split permissions and package state everywhere slowly and in slightly different ways.
+ *
+ * - With [RESTRICTION_REVOKED], we can now get rid of the error-prone logic about revoking and
+ * potentially re-granting permissions upon restriction state changes.
+ *
+ * Permission grants due to protection level are now tracked by [PROTECTION_GRANTED], and permission
+ * grants due to [PackageManager.grantRuntimePermission] are now tracked by [RUNTIME_GRANTED].
+ *
+ * The [PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED] and
+ * [PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED] flags are now unified into [IMPLICIT], and
+ * they can be differentiated by the presence of [LEGACY_GRANTED].
+ *
+ * The rest of the permission flags have a 1:1 mapping to the old `FLAG_PERMISSION_*` constants, and
+ * don't have any effect on the binary permission state.
+ */
object PermissionFlags {
+ /**
+ * Permission flag for a normal permission that is granted at package installation.
+ */
const val INSTALL_GRANTED = 1 shl 0
+
+ /**
+ * Permission flag for a normal permission that is revoked at package installation.
+ *
+ * Normally packages that have already been installed cannot be granted new normal permissions
+ * until its next installation (update), so this flag helps track that the normal permission was
+ * revoked upon its most recent installation.
+ */
const val INSTALL_REVOKED = 1 shl 1
+
+ /**
+ * Permission flag for a signature or internal permission that is granted based on the
+ * permission's protection level, including its protection and protection flags.
+ *
+ * For example, this flag may be set when the permission is a signature permission and the
+ * package is having a compatible signing certificate with the package defining the permission,
+ * or when the permission is a privileged permission and the package is a privileged app with
+ * its permission in the
+ * [privileged permission allowlist](https://source.android.com/docs/core/permissions/perms-allowlist).
+ */
const val PROTECTION_GRANTED = 1 shl 2
- const val ROLE_GRANTED = 1 shl 3
- // For permissions that are granted in other ways,
- // ex: via an API or implicit permissions that inherit from granted install permissions
- const val OTHER_GRANTED = 1 shl 4
- // For the permissions that are implicit for the package
- const val IMPLICIT = 1 shl 5
+ /**
+ * Permission flag for a role or runtime permission that is or was granted by a role.
+ *
+ * @see PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE
+ */
+ const val ROLE = 1 shl 3
+
+ /**
+ * Permission flag for a development, role or runtime permission that is granted via
+ * [PackageManager.grantRuntimePermission].
+ */
+ const val RUNTIME_GRANTED = 1 shl 4
+
+ /**
+ * Permission flag for a runtime permission whose state is set by the user.
+ *
+ * For example, this flag may be set when the permission is allowed by the user in the
+ * request permission dialog, or managed in the permission settings.
+ *
+ * @see PackageManager.FLAG_PERMISSION_USER_SET
+ */
+ const val USER_SET = 1 shl 5
+
+ /**
+ * Permission flag for a runtime permission whose state is (revoked and) fixed by the user.
+ *
+ * For example, this flag may be set when the permission is denied twice by the user in the
+ * request permission dialog.
+ *
+ * @see PackageManager.FLAG_PERMISSION_USER_FIXED
+ */
+ const val USER_FIXED = 1 shl 6
+
+ /**
+ * Permission flag for a runtime permission whose state is set and fixed by the device policy
+ * via [DevicePolicyManager.setPermissionGrantState].
+ *
+ * @see PackageManager.FLAG_PERMISSION_POLICY_FIXED
+ */
+ const val POLICY_FIXED = 1 shl 7
+
+ /**
+ * Permission flag for a runtime permission that is (pregranted and) fixed by the system.
+ *
+ * For example, this flag may be set in
+ * [com.android.server.pm.permission.DefaultPermissionGrantPolicy].
+ *
+ * @see PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
+ */
+ const val SYSTEM_FIXED = 1 shl 8
+
+ /**
+ * Permission flag for a runtime permission that is or was pregranted by the system.
+ *
+ * For example, this flag may be set in
+ * [com.android.server.pm.permission.DefaultPermissionGrantPolicy].
+ *
+ * @see PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
+ */
+ const val PREGRANT = 1 shl 9
+
+ /**
+ * Permission flag for a runtime permission that is granted because the package targets a legacy
+ * SDK version before [Build.VERSION_CODES.M] and doesn't support runtime permissions.
+ *
+ * As long as this flag is set, the permission should always be considered granted, although
+ * [APP_OP_REVOKED] may cause the app op for the runtime permission to be revoked. Once the
+ * package targets a higher SDK version so that it started supporting runtime permissions, this
+ * flag should be removed and the remaining flags should take effect.
+ *
+ * @see PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
+ * @see PackageManager.FLAG_PERMISSION_REVOKED_COMPAT
+ */
+ const val LEGACY_GRANTED = 1 shl 10
+
+ /**
+ * Permission flag for a runtime permission that is granted because the package targets a lower
+ * SDK version and the permission is implicit to it as a
+ * [split permission][PermissionManager.SplitPermissionInfo] from other non-runtime permissions.
+ *
+ * As long as this flag is set, the permission should always be considered granted, although
+ * [APP_OP_REVOKED] may cause the app op for the runtime permission to be revoked. Once the
+ * package targets a higher SDK version so that the permission is no longer implicit to it, this
+ * flag should be removed and the remaining flags should take effect.
+ *
+ * @see PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED
+ * @see PackageManager.FLAG_PERMISSION_REVOKED_COMPAT
+ */
+ const val IMPLICIT_GRANTED = 1 shl 11
+
+ /**
+ * Permission flag for a runtime permission that is granted because the package targets a legacy
+ * SDK version before [Build.VERSION_CODES.M] and doesn't support runtime permissions, so that
+ * it needs to be reviewed by the user; or granted because the package targets a lower SDK
+ * version and the permission is implicit to it as a
+ * [split permission][PermissionManager.SplitPermissionInfo] from other non-runtime permissions,
+ * so that it needs to be revoked when it's no longer implicit.
+ *
+ * @see PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
+ * @see PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED
+ */
+ const val IMPLICIT = 1 shl 12
+
+ /**
+ * Permission flag for a runtime permission that is user-sensitive when it's granted.
+ *
+ * This flag is informational and managed by PermissionController.
+ *
+ * @see PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED
+ */
+ const val USER_SENSITIVE_WHEN_GRANTED = 1 shl 13
+
+ /**
+ * Permission flag for a runtime permission that is user-sensitive when it's revoked.
+ *
+ * This flag is informational and managed by PermissionController.
+ *
+ * @see PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED
+ */
+ const val USER_SENSITIVE_WHEN_REVOKED = 1 shl 14
+
+ /**
+ * Permission flag for a restricted runtime permission that is exempt by the package's
+ * installer.
+ *
+ * For example, this flag may be set when the installer applied the exemption as part of the
+ * [session parameters](https://developer.android.com/reference/android/content/pm/PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(java.util.Set%3Cjava.lang.String%3E)).
+ *
+ * The permission will be restricted when none of the exempt flags is set.
+ *
+ * @see PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT
+ */
+ const val INSTALLER_EXEMPT = 1 shl 15
+
+ /**
+ * Permission flag for a restricted runtime permission that is exempt by the system.
+ *
+ * For example, this flag may be set when the package is a system app.
+ *
+ * The permission will be restricted when none of the exempt flags is set.
+ *
+ * @see PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT
+ */
+ const val SYSTEM_EXEMPT = 1 shl 16
+
+ /**
+ * Permission flag for a restricted runtime permission that is exempt due to system upgrade.
+ *
+ * For example, this flag may be set when the package was installed before the system was
+ * upgraded to [Build.VERSION_CODES.Q], when restricted permissions were introduced.
+ *
+ * The permission will be restricted when none of the exempt flags is set.
+ *
+ * @see PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT
+ */
+ const val UPGRADE_EXEMPT = 1 shl 17
+
+ /**
+ * Permission flag for a restricted runtime permission that is revoked due to being hard
+ * restricted.
+ *
+ * @see PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION
+ */
+ const val RESTRICTION_REVOKED = 1 shl 18
+
+ /**
+ * Permission flag for a restricted runtime permission that is soft restricted.
+ *
+ * @see PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION
+ */
+ const val SOFT_RESTRICTED = 1 shl 19
+
+ /**
+ * Permission flag for a runtime permission whose app op is revoked.
+ *
+ * For example, this flag may be set when the runtime permission is legacy or implicit but still
+ * "revoked" by the user in permission settings, or when the app op mode for the runtime
+ * permission is set to revoked via [AppOpsManager.setUidMode].
+ *
+ * @see PackageManager.FLAG_PERMISSION_REVOKED_COMPAT
+ */
+ const val APP_OP_REVOKED = 1 shl 20
+
+ /**
+ * Permission flag for a runtime permission that is granted as one-time.
+ *
+ * For example, this flag may be set when the user selected "Only this time" in the request
+ * permission dialog.
+ *
+ * This flag, along with other user decisions when it is set, should never be persisted, and
+ * should be removed once the permission is revoked.
+ *
+ * @see PackageManager.FLAG_PERMISSION_ONE_TIME
+ */
+ const val ONE_TIME = 1 shl 21
+
+ /**
+ * Permission flag for a runtime permission that was revoked due to app hibernation.
+ *
+ * This flag is informational and added by PermissionController, and should be removed once the
+ * permission is granted again.
+ *
+ * @see PackageManager.FLAG_PERMISSION_AUTO_REVOKED
+ */
+ const val HIBERNATION = 1 shl 22
+
+ /**
+ * Permission flag for a runtime permission that is selected by the user.
+ *
+ * For example, this flag may be set when one of the coarse/fine location accuracies is
+ * selected by the user.
+ *
+ * This flag is informational and managed by PermissionController.
+ *
+ * @see PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY
+ */
+ const val USER_SELECTED = 1 shl 23
+
+ /**
+ * Mask for all permission flags.
+ */
const val MASK_ALL = 0.inv()
- const val MASK_GRANTED = INSTALL_GRANTED or PROTECTION_GRANTED or OTHER_GRANTED or ROLE_GRANTED
- const val MASK_RUNTIME = OTHER_GRANTED or IMPLICIT
+
+ /**
+ * Mask for all permission flags that may be applied to a runtime permission.
+ */
+ const val MASK_RUNTIME = ROLE or RUNTIME_GRANTED or USER_SET or USER_FIXED or POLICY_FIXED or
+ SYSTEM_FIXED or PREGRANT or LEGACY_GRANTED or IMPLICIT_GRANTED or IMPLICIT or
+ USER_SENSITIVE_WHEN_GRANTED or USER_SENSITIVE_WHEN_REVOKED or INSTALLER_EXEMPT or
+ SYSTEM_EXEMPT or UPGRADE_EXEMPT or RESTRICTION_REVOKED or SOFT_RESTRICTED or
+ APP_OP_REVOKED or ONE_TIME or HIBERNATION or USER_SELECTED
+
+ /**
+ * Mask for all API permission flags about permission restriction.
+ */
+ private const val API_MASK_RESTRICTION =
+ PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT or
+ PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT or
+ PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT or
+ PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION
+
+ /**
+ * Mask for all permission flags about permission restriction.
+ */
+ private const val MASK_RESTRICTION = INSTALLER_EXEMPT or SYSTEM_EXEMPT or
+ UPGRADE_EXEMPT or RESTRICTION_REVOKED or SOFT_RESTRICTED
+
+ fun isPermissionGranted(policyFlags: Int): Boolean {
+ if (policyFlags.hasBits(INSTALL_GRANTED)) {
+ return true
+ }
+ if (policyFlags.hasBits(INSTALL_REVOKED)) {
+ return false
+ }
+ if (policyFlags.hasBits(PROTECTION_GRANTED)) {
+ return true
+ }
+ if (policyFlags.hasBits(LEGACY_GRANTED) || policyFlags.hasBits(IMPLICIT_GRANTED)) {
+ return true
+ }
+ if (policyFlags.hasBits(RESTRICTION_REVOKED)) {
+ return false
+ }
+ return policyFlags.hasBits(RUNTIME_GRANTED)
+ }
+
+ fun isAppOpGranted(policyFlags: Int): Boolean =
+ isPermissionGranted(policyFlags) && !policyFlags.hasBits(APP_OP_REVOKED)
+
+ fun isReviewRequired(policyFlags: Int): Boolean =
+ policyFlags.hasBits(LEGACY_GRANTED) && policyFlags.hasBits(IMPLICIT)
+
+ fun toApiFlags(policyFlags: Int): Int {
+ var apiFlags = 0
+ if (policyFlags.hasBits(USER_SET)) {
+ apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_USER_SET
+ }
+ if (policyFlags.hasBits(USER_FIXED)) {
+ apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_USER_FIXED
+ }
+ if (policyFlags.hasBits(POLICY_FIXED)) {
+ apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_POLICY_FIXED
+ }
+ if (policyFlags.hasBits(SYSTEM_FIXED)) {
+ apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
+ }
+ if (policyFlags.hasBits(PREGRANT)) {
+ apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT
+ }
+ if (policyFlags.hasBits(IMPLICIT)) {
+ apiFlags = apiFlags or if (policyFlags.hasBits(LEGACY_GRANTED)) {
+ PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
+ } else {
+ PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED
+ }
+ }
+ if (policyFlags.hasBits(USER_SENSITIVE_WHEN_GRANTED)) {
+ apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED
+ }
+ if (policyFlags.hasBits(USER_SENSITIVE_WHEN_REVOKED)) {
+ apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED
+ }
+ if (policyFlags.hasBits(INSTALLER_EXEMPT)) {
+ apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT
+ }
+ if (policyFlags.hasBits(SYSTEM_EXEMPT)) {
+ apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT
+ }
+ if (policyFlags.hasBits(UPGRADE_EXEMPT)) {
+ apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT
+ }
+ if (policyFlags.hasBits(RESTRICTION_REVOKED) || policyFlags.hasBits(SOFT_RESTRICTED)) {
+ apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION
+ }
+ if (policyFlags.hasBits(ROLE)) {
+ apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE
+ }
+ if (policyFlags.hasBits(APP_OP_REVOKED)) {
+ apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_REVOKED_COMPAT
+ }
+ if (policyFlags.hasBits(ONE_TIME)) {
+ apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_ONE_TIME
+ }
+ if (policyFlags.hasBits(HIBERNATION)) {
+ apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_AUTO_REVOKED
+ }
+ if (policyFlags.hasBits(USER_SELECTED)) {
+ apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY
+ }
+ return apiFlags
+ }
+
+ fun setRuntimePermissionGranted(policyFlags: Int, isGranted: Boolean): Int =
+ if (isGranted) policyFlags or RUNTIME_GRANTED else policyFlags andInv RUNTIME_GRANTED
+
+ fun updatePolicyFlags(policyFlags: Int, apiFlagMask: Int, apiFlagValues: Int): Int {
+ check(!apiFlagMask.hasAnyBit(API_MASK_RESTRICTION)) {
+ "Permission flags about permission restriction can only be directly mutated by the" +
+ " policy"
+ }
+ val oldApiFlags = toApiFlags(policyFlags)
+ val newApiFlags = (oldApiFlags andInv apiFlagMask) or (apiFlagValues and apiFlagMask)
+ return toPolicyFlags(policyFlags, newApiFlags)
+ }
+
+ private fun toPolicyFlags(oldPolicyFlags: Int, apiFlags: Int): Int {
+ var policyFlags = 0
+ policyFlags = policyFlags or (oldPolicyFlags and INSTALL_GRANTED)
+ policyFlags = policyFlags or (oldPolicyFlags and INSTALL_REVOKED)
+ policyFlags = policyFlags or (oldPolicyFlags and PROTECTION_GRANTED)
+ if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE)) {
+ policyFlags = policyFlags or ROLE
+ }
+ policyFlags = policyFlags or (oldPolicyFlags and RUNTIME_GRANTED)
+ if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_USER_SET)) {
+ policyFlags = policyFlags or USER_SET
+ }
+ if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_USER_FIXED)) {
+ policyFlags = policyFlags or USER_FIXED
+ }
+ if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_POLICY_FIXED)) {
+ policyFlags = policyFlags or POLICY_FIXED
+ }
+ if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_SYSTEM_FIXED)) {
+ policyFlags = policyFlags or SYSTEM_FIXED
+ }
+ if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT)) {
+ policyFlags = policyFlags or PREGRANT
+ }
+ policyFlags = policyFlags or (oldPolicyFlags and LEGACY_GRANTED)
+ policyFlags = policyFlags or (oldPolicyFlags and IMPLICIT_GRANTED)
+ if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) ||
+ apiFlags.hasBits(PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED)) {
+ policyFlags = policyFlags or IMPLICIT
+ }
+ if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED)) {
+ policyFlags = policyFlags or USER_SENSITIVE_WHEN_GRANTED
+ }
+ if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED)) {
+ policyFlags = policyFlags or USER_SENSITIVE_WHEN_REVOKED
+ }
+ // FLAG_PERMISSION_APPLY_RESTRICTION can be either REVOKED_BY_RESTRICTION when the
+ // permission is hard restricted, or SOFT_RESTRICTED when the permission is soft restricted.
+ // However since we should never allow indirect mutation of restriction state, we can just
+ // get the flags about restriction from the old policy flags.
+ policyFlags = policyFlags or (oldPolicyFlags and MASK_RESTRICTION)
+ if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_REVOKED_COMPAT)) {
+ policyFlags = policyFlags or APP_OP_REVOKED
+ }
+ if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_ONE_TIME)) {
+ policyFlags = policyFlags or ONE_TIME
+ }
+ if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_AUTO_REVOKED)) {
+ policyFlags = policyFlags or HIBERNATION
+ }
+ if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY)) {
+ policyFlags = policyFlags or USER_SELECTED
+ }
+ return policyFlags
+ }
}
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index f04734caedba..82017362da29 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -232,7 +232,12 @@ class PermissionService(
}
override fun getPermissionFlags(packageName: String, permissionName: String, userId: Int): Int {
- TODO("Not yet implemented")
+ // TODO: Implement permission checks.
+ val appId = 0
+ val flags = service.getState {
+ with(policy) { getPermissionFlags(appId, userId, permissionName) }
+ }
+ return PermissionFlags.toApiFlags(flags)
}
override fun isPermissionRevokedByPolicy(
@@ -244,7 +249,12 @@ class PermissionService(
}
override fun isPermissionsReviewRequired(packageName: String, userId: Int): Boolean {
- TODO("Not yet implemented")
+ val packageState = packageManagerLocal.withUnfilteredSnapshot()
+ .use { it.packageStates[packageName] } ?: return false
+ val permissionFlags = service.getState {
+ with(policy) { getUidPermissionFlags(packageState.appId, userId) }
+ } ?: return false
+ return permissionFlags.anyIndexed { _, _, flags -> PermissionFlags.isReviewRequired(flags) }
}
override fun shouldShowRequestPermissionRationale(
diff --git a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
index a17a31797270..b2f52cc814cb 100644
--- a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
@@ -37,7 +37,6 @@ import com.android.server.permission.access.SystemState
import com.android.server.permission.access.UidUri
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
import com.android.server.permission.access.util.andInv
-import com.android.server.permission.access.util.hasAnyBit
import com.android.server.permission.access.util.hasBits
import com.android.server.pm.KnownPackages
import com.android.server.pm.parsing.PackageInfoUtils
@@ -474,10 +473,10 @@ class UidPermissionPolicy : SchemePolicy() {
// should only affect the other static flags, but not dynamic flags like development or
// role. This may be useful in the case of an updated system app.
if (permission.isDevelopment) {
- newFlags = newFlags or (oldFlags and PermissionFlags.OTHER_GRANTED)
+ newFlags = newFlags or (oldFlags and PermissionFlags.RUNTIME_GRANTED)
}
if (permission.isRole) {
- newFlags = newFlags or (oldFlags and PermissionFlags.ROLE_GRANTED)
+ newFlags = newFlags or (oldFlags and PermissionFlags.ROLE)
}
setPermissionFlags(appId, userId, permissionName, newFlags)
} else if (permission.isRuntime) {
@@ -519,8 +518,8 @@ class UidPermissionPolicy : SchemePolicy() {
"Unknown source permission $sourcePermissionName in split permissions"
}
val sourceFlags = getPermissionFlags(appId, userId, sourcePermissionName)
- val isSourceGranted = sourceFlags.hasAnyBit(PermissionFlags.MASK_GRANTED)
- val isNewGranted = newFlags.hasAnyBit(PermissionFlags.MASK_GRANTED)
+ val isSourceGranted = PermissionFlags.isPermissionGranted(sourceFlags)
+ val isNewGranted = PermissionFlags.isPermissionGranted(newFlags)
val isGrantingNewFromRevoke = isSourceGranted && !isNewGranted
if (isSourceGranted == isNewGranted || isGrantingNewFromRevoke) {
if (isGrantingNewFromRevoke) {
@@ -528,7 +527,7 @@ class UidPermissionPolicy : SchemePolicy() {
}
newFlags = newFlags or (sourceFlags and PermissionFlags.MASK_RUNTIME)
if (!sourcePermission.isRuntime && isSourceGranted) {
- newFlags = newFlags or PermissionFlags.OTHER_GRANTED
+ newFlags = newFlags or PermissionFlags.IMPLICIT_GRANTED
}
}
}
@@ -836,6 +835,9 @@ class UidPermissionPolicy : SchemePolicy() {
fun GetStateScope.getPermission(permissionName: String): Permission? =
state.systemState.permissions[permissionName]
+ fun GetStateScope.getUidPermissionFlags(appId: Int, userId: Int): IndexedMap<String, Int>? =
+ state.userStates[userId]?.uidPermissionFlags?.get(appId)
+
fun GetStateScope.getPermissionFlags(
appId: Int,
userId: Int,
@@ -854,7 +856,8 @@ class UidPermissionPolicy : SchemePolicy() {
appId: Int,
userId: Int,
permissionName: String
- ): Int = state.userStates[userId].uidPermissionFlags[appId].getWithDefault(permissionName, 0)
+ ): Int =
+ state.userStates[userId]?.uidPermissionFlags?.get(appId).getWithDefault(permissionName, 0)
fun MutateStateScope.setPermissionFlags(
appId: Int,
@@ -875,7 +878,7 @@ class UidPermissionPolicy : SchemePolicy() {
val uidPermissionFlags = userState.uidPermissionFlags
var permissionFlags = uidPermissionFlags[appId]
val oldFlags = permissionFlags.getWithDefault(permissionName, 0)
- val newFlags = (oldFlags andInv flagMask) or flagValues
+ val newFlags = (oldFlags andInv flagMask) or (flagValues and flagMask)
if (oldFlags == newFlags) {
return false
}
diff --git a/services/proguard.flags b/services/proguard.flags
index 6cdf11c3c685..ba4560f1e8dc 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -78,6 +78,13 @@
-keep,allowoptimization,allowaccessmodification class com.android.server.wm.** implements com.android.server.wm.DisplayAreaPolicy$Provider
# JNI keep rules
+# The global keep rule for native methods allows stripping of such methods if they're unreferenced
+# in Java. However, because system_server explicitly registers these methods from native code,
+# stripping them in Java can cause runtime issues. As such, conservatively keep all such methods in
+# system_server subpackages as long as the containing class is also kept or referenced.
+-keepclassmembers class com.android.server.** {
+ native <methods>;
+}
# TODO(b/210510433): Revisit and fix with @Keep, or consider auto-generating from
# frameworks/base/services/core/jni/onload.cpp.
-keep,allowoptimization,allowaccessmodification class com.android.server.broadcastradio.hal1.BroadcastRadioService { *; }
diff --git a/services/tests/PackageManagerServiceTests/server/Android.bp b/services/tests/PackageManagerServiceTests/server/Android.bp
index cc2659308a95..ebd6b649c4a8 100644
--- a/services/tests/PackageManagerServiceTests/server/Android.bp
+++ b/services/tests/PackageManagerServiceTests/server/Android.bp
@@ -94,7 +94,6 @@ android_test {
"libunwindstack",
"libutils",
"netd_aidl_interface-V5-cpp",
- "libservices.core.settings.testonly",
],
dxflags: ["--multi-dex"],
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index b3f64b6db54c..3727d660f9a7 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -111,10 +111,6 @@ public class PackageManagerSettingsTests {
private static final String PACKAGE_NAME_3 = "com.android.app3";
private static final int TEST_RESOURCE_ID = 2131231283;
- static {
- System.loadLibrary("services.core.settings.testonly");
- }
-
@Mock
RuntimePermissionsPersistence mRuntimePermissionsPersistence;
@Mock
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
index be13bad70e16..021d01cca381 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
@@ -48,7 +48,7 @@ public class AppOpsLegacyRestrictionsTest {
StaticMockitoSession mSession;
@Mock
- AppOpsServiceImpl.Constants mConstants;
+ AppOpsService.Constants mConstants;
@Mock
Context mContext;
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
index 7d4bc6f47ad4..c0688d131610 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
@@ -22,8 +22,6 @@ import static android.app.AppOpsManager.OP_FLAGS_ALL;
import static android.app.AppOpsManager.OP_READ_SMS;
import static android.app.AppOpsManager.OP_WIFI_SCAN;
import static android.app.AppOpsManager.OP_WRITE_SMS;
-import static android.app.AppOpsManager.resolvePackageName;
-import static android.os.Process.INVALID_UID;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -41,7 +39,6 @@ import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
-import android.app.AppOpsManager;
import android.app.AppOpsManager.OpEntry;
import android.app.AppOpsManager.PackageOps;
import android.content.ContentResolver;
@@ -89,13 +86,13 @@ public class AppOpsServiceTest {
private File mAppOpsFile;
private Handler mHandler;
- private AppOpsServiceImpl mAppOpsService;
+ private AppOpsService mAppOpsService;
private int mMyUid;
private long mTestStartMillis;
private StaticMockitoSession mMockingSession;
private void setupAppOpsService() {
- mAppOpsService = new AppOpsServiceImpl(mAppOpsFile, mHandler, spy(sContext));
+ mAppOpsService = new AppOpsService(mAppOpsFile, mHandler, spy(sContext));
mAppOpsService.mHistoricalRegistry.systemReady(sContext.getContentResolver());
// Always approve all permission checks
@@ -164,20 +161,17 @@ public class AppOpsServiceTest {
@Test
public void testNoteOperationAndGetOpsForPackage() {
- mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
- mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED, null);
+ mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
+ mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED);
// Note an op that's allowed.
- mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
- resolvePackageName(mMyUid, sMyPackageName), null,
- INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
+ mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
List<PackageOps> loggedOps = getLoggedOps();
assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
// Note another op that's not allowed.
- mAppOpsService.noteOperationUnchecked(OP_WRITE_SMS, mMyUid,
- resolvePackageName(mMyUid, sMyPackageName), null,
- INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
+ mAppOpsService.noteOperation(OP_WRITE_SMS, mMyUid, sMyPackageName, null, false, null,
+ false);
loggedOps = getLoggedOps();
assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
assertContainsOp(loggedOps, OP_WRITE_SMS, -1, mTestStartMillis, MODE_ERRORED);
@@ -191,20 +185,18 @@ public class AppOpsServiceTest {
@Test
public void testNoteOperationAndGetOpsForPackage_controlledByDifferentOp() {
// This op controls WIFI_SCAN
- mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ALLOWED, null);
+ mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ALLOWED);
- assertThat(mAppOpsService.noteOperationUnchecked(OP_WIFI_SCAN, mMyUid,
- resolvePackageName(mMyUid, sMyPackageName), null,
- INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF)).isEqualTo(MODE_ALLOWED);
+ assertThat(mAppOpsService.noteOperation(OP_WIFI_SCAN, mMyUid, sMyPackageName, null, false,
+ null, false).getOpMode()).isEqualTo(MODE_ALLOWED);
assertContainsOp(getLoggedOps(), OP_WIFI_SCAN, mTestStartMillis, -1,
MODE_ALLOWED /* default for WIFI_SCAN; this is not changed or used in this test */);
// Now set COARSE_LOCATION to ERRORED -> this will make WIFI_SCAN disabled as well.
- mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ERRORED, null);
- assertThat(mAppOpsService.noteOperationUnchecked(OP_WIFI_SCAN, mMyUid,
- resolvePackageName(mMyUid, sMyPackageName), null,
- INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF)).isEqualTo(MODE_ERRORED);
+ mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ERRORED);
+ assertThat(mAppOpsService.noteOperation(OP_WIFI_SCAN, mMyUid, sMyPackageName, null, false,
+ null, false).getOpMode()).isEqualTo(MODE_ERRORED);
assertContainsOp(getLoggedOps(), OP_WIFI_SCAN, mTestStartMillis, mTestStartMillis,
MODE_ALLOWED /* default for WIFI_SCAN; this is not changed or used in this test */);
@@ -213,14 +205,11 @@ public class AppOpsServiceTest {
// Tests the dumping and restoring of the in-memory state to/from XML.
@Test
public void testStatePersistence() {
- mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
- mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED, null);
- mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
- resolvePackageName(mMyUid, sMyPackageName), null,
- INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
- mAppOpsService.noteOperationUnchecked(OP_WRITE_SMS, mMyUid,
- resolvePackageName(mMyUid, sMyPackageName), null,
- INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
+ mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
+ mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED);
+ mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
+ mAppOpsService.noteOperation(OP_WRITE_SMS, mMyUid, sMyPackageName, null, false, null,
+ false);
mAppOpsService.writeState();
// Create a new app ops service which will initialize its state from XML.
@@ -235,10 +224,8 @@ public class AppOpsServiceTest {
// Tests that ops are persisted during shutdown.
@Test
public void testShutdown() {
- mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
- mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
- resolvePackageName(mMyUid, sMyPackageName), null,
- INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
+ mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
+ mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
mAppOpsService.shutdown();
// Create a new app ops service which will initialize its state from XML.
@@ -251,10 +238,8 @@ public class AppOpsServiceTest {
@Test
public void testGetOpsForPackage() {
- mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
- mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
- resolvePackageName(mMyUid, sMyPackageName), null,
- INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
+ mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
+ mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
// Query all ops
List<PackageOps> loggedOps = mAppOpsService.getOpsForPackage(
@@ -282,10 +267,8 @@ public class AppOpsServiceTest {
@Test
public void testPackageRemoved() {
- mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
- mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
- resolvePackageName(mMyUid, sMyPackageName), null,
- INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
+ mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
+ mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
List<PackageOps> loggedOps = getLoggedOps();
assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
@@ -339,10 +322,8 @@ public class AppOpsServiceTest {
@Test
public void testUidRemoved() {
- mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
- mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
- resolvePackageName(mMyUid, sMyPackageName), null,
- INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
+ mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
+ mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
List<PackageOps> loggedOps = getLoggedOps();
assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
index 3efd5e701013..98e895a86f9e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
@@ -76,7 +76,7 @@ public class AppOpsUidStateTrackerTest {
ActivityManagerInternal mAmi;
@Mock
- AppOpsServiceImpl.Constants mConstants;
+ AppOpsService.Constants mConstants;
AppOpsUidStateTrackerTestExecutor mExecutor = new AppOpsUidStateTrackerTestExecutor();
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
index 302fa0f0c528..9eed6ada3a37 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
@@ -121,13 +121,13 @@ public class AppOpsUpgradeTest {
}
}
- private void assertSameModes(SparseArray<AppOpsServiceImpl.UidState> uidStates,
+ private void assertSameModes(SparseArray<AppOpsService.UidState> uidStates,
int op1, int op2) {
int numberOfNonDefaultOps = 0;
final int defaultModeOp1 = AppOpsManager.opToDefaultMode(op1);
final int defaultModeOp2 = AppOpsManager.opToDefaultMode(op2);
for (int i = 0; i < uidStates.size(); i++) {
- final AppOpsServiceImpl.UidState uidState = uidStates.valueAt(i);
+ final AppOpsService.UidState uidState = uidStates.valueAt(i);
SparseIntArray opModes = uidState.getNonDefaultUidModes();
if (opModes != null) {
final int uidMode1 = opModes.get(op1, defaultModeOp1);
@@ -141,12 +141,12 @@ public class AppOpsUpgradeTest {
continue;
}
for (int j = 0; j < uidState.pkgOps.size(); j++) {
- final AppOpsServiceImpl.Ops ops = uidState.pkgOps.valueAt(j);
+ final AppOpsService.Ops ops = uidState.pkgOps.valueAt(j);
if (ops == null) {
continue;
}
- final AppOpsServiceImpl.Op _op1 = ops.get(op1);
- final AppOpsServiceImpl.Op _op2 = ops.get(op2);
+ final AppOpsService.Op _op1 = ops.get(op1);
+ final AppOpsService.Op _op2 = ops.get(op2);
final int mode1 = (_op1 == null) ? defaultModeOp1 : _op1.getMode();
final int mode2 = (_op2 == null) ? defaultModeOp2 : _op2.getMode();
assertEquals(mode1, mode2);
@@ -199,7 +199,7 @@ public class AppOpsUpgradeTest {
public void upgradeRunAnyInBackground() {
extractAppOpsFile(APP_OPS_UNVERSIONED_ASSET_PATH);
- AppOpsServiceImpl testService = new AppOpsServiceImpl(sAppOpsFile, mHandler, mTestContext);
+ AppOpsService testService = new AppOpsService(sAppOpsFile, mHandler, mTestContext);
testService.upgradeRunAnyInBackgroundLocked();
assertSameModes(testService.mUidStates, AppOpsManager.OP_RUN_IN_BACKGROUND,
@@ -244,7 +244,7 @@ public class AppOpsUpgradeTest {
return UserHandle.getUid(userId, appIds[index]);
}).when(mPackageManagerInternal).getPackageUid(anyString(), anyLong(), anyInt());
- AppOpsServiceImpl testService = new AppOpsServiceImpl(sAppOpsFile, mHandler, mTestContext);
+ AppOpsService testService = new AppOpsService(sAppOpsFile, mHandler, mTestContext);
testService.upgradeScheduleExactAlarmLocked();
@@ -259,7 +259,7 @@ public class AppOpsUpgradeTest {
} else {
expectedMode = previousMode;
}
- final AppOpsServiceImpl.UidState uidState = testService.mUidStates.get(uid);
+ final AppOpsService.UidState uidState = testService.mUidStates.get(uid);
assertEquals(expectedMode, uidState.getUidMode(OP_SCHEDULE_EXACT_ALARM));
}
}
@@ -268,7 +268,7 @@ public class AppOpsUpgradeTest {
int[] unrelatedUidsInFile = {10225, 10178};
for (int uid : unrelatedUidsInFile) {
- final AppOpsServiceImpl.UidState uidState = testService.mUidStates.get(uid);
+ final AppOpsService.UidState uidState = testService.mUidStates.get(uid);
assertEquals(AppOpsManager.opToDefaultMode(OP_SCHEDULE_EXACT_ALARM),
uidState.getUidMode(OP_SCHEDULE_EXACT_ALARM));
}
@@ -278,8 +278,8 @@ public class AppOpsUpgradeTest {
public void upgradeFromNoFile() {
assertFalse(sAppOpsFile.exists());
- AppOpsServiceImpl testService = spy(
- new AppOpsServiceImpl(sAppOpsFile, mHandler, mTestContext));
+ AppOpsService testService = spy(
+ new AppOpsService(sAppOpsFile, mHandler, mTestContext));
doNothing().when(testService).upgradeRunAnyInBackgroundLocked();
doNothing().when(testService).upgradeScheduleExactAlarmLocked();
@@ -296,7 +296,7 @@ public class AppOpsUpgradeTest {
AppOpsDataParser parser = new AppOpsDataParser(sAppOpsFile);
assertTrue(parser.parse());
- assertEquals(AppOpsServiceImpl.CURRENT_VERSION, parser.mVersion);
+ assertEquals(AppOpsService.CURRENT_VERSION, parser.mVersion);
}
@Test
@@ -306,8 +306,8 @@ public class AppOpsUpgradeTest {
assertTrue(parser.parse());
assertEquals(AppOpsDataParser.NO_VERSION, parser.mVersion);
- AppOpsServiceImpl testService = spy(
- new AppOpsServiceImpl(sAppOpsFile, mHandler, mTestContext));
+ AppOpsService testService = spy(
+ new AppOpsService(sAppOpsFile, mHandler, mTestContext));
doNothing().when(testService).upgradeRunAnyInBackgroundLocked();
doNothing().when(testService).upgradeScheduleExactAlarmLocked();
@@ -320,7 +320,7 @@ public class AppOpsUpgradeTest {
testService.writeState();
assertTrue(parser.parse());
- assertEquals(AppOpsServiceImpl.CURRENT_VERSION, parser.mVersion);
+ assertEquals(AppOpsService.CURRENT_VERSION, parser.mVersion);
}
@Test
@@ -330,8 +330,8 @@ public class AppOpsUpgradeTest {
assertTrue(parser.parse());
assertEquals(1, parser.mVersion);
- AppOpsServiceImpl testService = spy(
- new AppOpsServiceImpl(sAppOpsFile, mHandler, mTestContext));
+ AppOpsService testService = spy(
+ new AppOpsService(sAppOpsFile, mHandler, mTestContext));
doNothing().when(testService).upgradeRunAnyInBackgroundLocked();
doNothing().when(testService).upgradeScheduleExactAlarmLocked();
@@ -344,7 +344,7 @@ public class AppOpsUpgradeTest {
testService.writeState();
assertTrue(parser.parse());
- assertEquals(AppOpsServiceImpl.CURRENT_VERSION, parser.mVersion);
+ assertEquals(AppOpsService.CURRENT_VERSION, parser.mVersion);
}
/**
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/state/DisplayStateControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/state/DisplayStateControllerTest.java
new file mode 100644
index 000000000000..880501f39ac2
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/display/state/DisplayStateControllerTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.state;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.display.DisplayManagerInternal;
+import android.view.Display;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.display.DisplayPowerProximityStateController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class DisplayStateControllerTest {
+ private static final boolean DISPLAY_ENABLED = true;
+ private static final boolean DISPLAY_IN_TRANSITION = true;
+
+ private DisplayStateController mDisplayStateController;
+
+ @Mock
+ private DisplayPowerProximityStateController mDisplayPowerProximityStateController;
+
+ @Before
+ public void before() {
+ MockitoAnnotations.initMocks(this);
+ mDisplayStateController = new DisplayStateController(mDisplayPowerProximityStateController);
+ }
+
+ @Test
+ public void updateProximityStateEvaluatesStateOffPolicyAsExpected() {
+ when(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()).thenReturn(
+ false);
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+ DisplayManagerInternal.DisplayPowerRequest.class);
+
+ displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
+ int state = mDisplayStateController.updateDisplayState(displayPowerRequest, DISPLAY_ENABLED,
+ !DISPLAY_IN_TRANSITION);
+ assertEquals(Display.STATE_OFF, state);
+ verify(mDisplayPowerProximityStateController).updateProximityState(displayPowerRequest,
+ Display.STATE_OFF);
+ assertEquals(true, mDisplayStateController.shouldPerformScreenOffTransition());
+ }
+
+ @Test
+ public void updateProximityStateEvaluatesDozePolicyAsExpected() {
+ when(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()).thenReturn(
+ false);
+ validDisplayState(DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE,
+ Display.STATE_DOZE, DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
+ }
+
+ @Test
+ public void updateProximityStateEvaluatesDimPolicyAsExpected() {
+ when(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()).thenReturn(
+ false);
+ validDisplayState(DisplayManagerInternal.DisplayPowerRequest.POLICY_DIM,
+ Display.STATE_ON, DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
+ }
+
+ @Test
+ public void updateProximityStateEvaluatesDimBrightAsExpected() {
+ when(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()).thenReturn(
+ false);
+ validDisplayState(DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT,
+ Display.STATE_ON, DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
+ }
+
+ @Test
+ public void updateProximityStateWorksAsExpectedWhenDisplayDisabled() {
+ when(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()).thenReturn(
+ false);
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+ DisplayManagerInternal.DisplayPowerRequest.class);
+
+ displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+ int state = mDisplayStateController.updateDisplayState(displayPowerRequest,
+ !DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
+ assertEquals(Display.STATE_OFF, state);
+ verify(mDisplayPowerProximityStateController).updateProximityState(displayPowerRequest,
+ Display.STATE_ON);
+ assertEquals(false, mDisplayStateController.shouldPerformScreenOffTransition());
+ }
+
+ @Test
+ public void updateProximityStateWorksAsExpectedWhenTransitionPhase() {
+ when(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()).thenReturn(
+ false);
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+ DisplayManagerInternal.DisplayPowerRequest.class);
+
+ displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+ int state = mDisplayStateController.updateDisplayState(displayPowerRequest, DISPLAY_ENABLED,
+ DISPLAY_IN_TRANSITION);
+ assertEquals(Display.STATE_OFF, state);
+ verify(mDisplayPowerProximityStateController).updateProximityState(displayPowerRequest,
+ Display.STATE_ON);
+ assertEquals(false, mDisplayStateController.shouldPerformScreenOffTransition());
+ }
+
+ @Test
+ public void updateProximityStateWorksAsExpectedWhenScreenOffBecauseOfProximity() {
+ when(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()).thenReturn(
+ true);
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+ DisplayManagerInternal.DisplayPowerRequest.class);
+
+ displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+ int state = mDisplayStateController.updateDisplayState(displayPowerRequest, DISPLAY_ENABLED,
+ !DISPLAY_IN_TRANSITION);
+ assertEquals(Display.STATE_OFF, state);
+ verify(mDisplayPowerProximityStateController).updateProximityState(displayPowerRequest,
+ Display.STATE_ON);
+ assertEquals(false, mDisplayStateController.shouldPerformScreenOffTransition());
+ }
+
+ private void validDisplayState(int policy, int displayState, boolean isEnabled,
+ boolean isInTransition) {
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+ DisplayManagerInternal.DisplayPowerRequest.class);
+ displayPowerRequest.policy = policy;
+ int state = mDisplayStateController.updateDisplayState(displayPowerRequest, isEnabled,
+ isInTransition);
+ assertEquals(displayState, state);
+ verify(mDisplayPowerProximityStateController).updateProximityState(displayPowerRequest,
+ displayState);
+ assertEquals(false, mDisplayStateController.shouldPerformScreenOffTransition());
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
index a9dc4af07bab..480a4f358bc0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
@@ -217,7 +217,7 @@ public final class JobConcurrencyManagerTest {
assertEquals(0, preferredUidOnly.size());
assertEquals(0, stoppable.size());
assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
- assertEquals(0, assignmentInfo.numRunningTopEj);
+ assertEquals(0, assignmentInfo.numRunningImmediacyPrivileged);
}
@Test
@@ -239,7 +239,7 @@ public final class JobConcurrencyManagerTest {
assertEquals(0, preferredUidOnly.size());
assertEquals(0, stoppable.size());
assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
- assertEquals(0, assignmentInfo.numRunningTopEj);
+ assertEquals(0, assignmentInfo.numRunningImmediacyPrivileged);
}
@Test
@@ -265,15 +265,14 @@ public final class JobConcurrencyManagerTest {
assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
assertEquals(0, stoppable.size());
assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
- assertEquals(0, assignmentInfo.numRunningTopEj);
+ assertEquals(0, assignmentInfo.numRunningImmediacyPrivileged);
}
@Test
- public void testPrepareForAssignmentDetermination_onlyRunningTopEjs() {
+ public void testPrepareForAssignmentDetermination_onlyStartedWithImmediacyPrivilege() {
for (int i = 0; i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT; ++i) {
JobStatus job = createJob(mDefaultUserId * UserHandle.PER_USER_RANGE + i);
- job.startedAsExpeditedJob = true;
- job.lastEvaluatedBias = JobInfo.BIAS_TOP_APP;
+ job.startedWithImmediacyPrivilege = true;
mJobConcurrencyManager.addRunningJobForTesting(job);
}
@@ -294,7 +293,7 @@ public final class JobConcurrencyManagerTest {
assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT / 2, stoppable.size());
assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT,
- assignmentInfo.numRunningTopEj);
+ assignmentInfo.numRunningImmediacyPrivileged);
}
@Test
@@ -500,6 +499,38 @@ public final class JobConcurrencyManagerTest {
}
@Test
+ public void testHasImmediacyPrivilege() {
+ JobStatus job = createJob(mDefaultUserId * UserHandle.PER_USER_RANGE, 0);
+ spyOn(job);
+ assertFalse(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job));
+
+ doReturn(false).when(job).shouldTreatAsExpeditedJob();
+ doReturn(false).when(job).shouldTreatAsUserInitiated();
+ job.lastEvaluatedBias = JobInfo.BIAS_TOP_APP;
+ assertFalse(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job));
+
+ doReturn(true).when(job).shouldTreatAsExpeditedJob();
+ doReturn(false).when(job).shouldTreatAsUserInitiated();
+ job.lastEvaluatedBias = JobInfo.BIAS_DEFAULT;
+ assertFalse(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job));
+
+ doReturn(false).when(job).shouldTreatAsExpeditedJob();
+ doReturn(true).when(job).shouldTreatAsUserInitiated();
+ job.lastEvaluatedBias = JobInfo.BIAS_DEFAULT;
+ assertFalse(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job));
+
+ doReturn(false).when(job).shouldTreatAsExpeditedJob();
+ doReturn(true).when(job).shouldTreatAsUserInitiated();
+ job.lastEvaluatedBias = JobInfo.BIAS_TOP_APP;
+ assertTrue(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job));
+
+ doReturn(true).when(job).shouldTreatAsExpeditedJob();
+ doReturn(false).when(job).shouldTreatAsUserInitiated();
+ job.lastEvaluatedBias = JobInfo.BIAS_TOP_APP;
+ assertTrue(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job));
+ }
+
+ @Test
public void testIsPkgConcurrencyLimited_top() {
final JobStatus topJob = createJob(mDefaultUserId * UserHandle.PER_USER_RANGE, 0);
topJob.lastEvaluatedBias = JobInfo.BIAS_TOP_APP;
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
index 6d8910eb8cdd..58cff944868c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
@@ -107,8 +107,8 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator
onVisible(PROFILE_USER_ID));
startForegroundUser(PARENT_USER_ID);
- int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
- DEFAULT_DISPLAY);
+ int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
+ BG_VISIBLE, DEFAULT_DISPLAY);
assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
expectUserIsVisible(PROFILE_USER_ID);
@@ -138,7 +138,8 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator
public void testStartBgUser_onInvalidDisplay() throws Exception {
AsyncUserVisibilityListener listener = addListenerForNoEvents();
- int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG, INVALID_DISPLAY);
+ int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
+ INVALID_DISPLAY);
assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
@@ -151,7 +152,7 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator
public void testStartBgUser_onSecondaryDisplay_displayAvailable() throws Exception {
AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(USER_ID));
- int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG,
+ int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
SECONDARY_DISPLAY_ID);
assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
@@ -176,7 +177,7 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator
expectUserIsNotVisibleOnDisplay("before", PARENT_USER_ID, SECONDARY_DISPLAY_ID);
expectUserIsNotVisibleOnDisplay("before", PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
- int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG,
+ int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
SECONDARY_DISPLAY_ID);
assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
@@ -189,7 +190,7 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator
AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(OTHER_USER_ID));
startUserInSecondaryDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID);
- int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG,
+ int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
SECONDARY_DISPLAY_ID);
assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
@@ -205,7 +206,7 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator
AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(USER_ID));
startUserInSecondaryDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID);
- int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG,
+ int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
SECONDARY_DISPLAY_ID);
assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
@@ -228,8 +229,8 @@ public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediator
AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(PARENT_USER_ID));
startUserInSecondaryDisplay(PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID);
- int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
- DEFAULT_DISPLAY);
+ int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
+ BG_VISIBLE, DEFAULT_DISPLAY);
assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
index 1065392be8ce..3d64c29e463c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
@@ -106,8 +106,8 @@ public final class UserVisibilityMediatorSUSDTest extends UserVisibilityMediator
onVisible(PROFILE_USER_ID));
startForegroundUser(PARENT_USER_ID);
- int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
- DEFAULT_DISPLAY);
+ int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
+ BG_VISIBLE, DEFAULT_DISPLAY);
assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
expectUserIsVisible(PROFILE_USER_ID);
@@ -126,7 +126,7 @@ public final class UserVisibilityMediatorSUSDTest extends UserVisibilityMediator
public void testStartBgUser_onSecondaryDisplay() throws Exception {
AsyncUserVisibilityListener listener = addListenerForNoEvents();
- int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG,
+ int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
SECONDARY_DISPLAY_ID);
assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
index 4487d136b708..74fd9ff3163c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
@@ -26,6 +26,9 @@ import static android.view.Display.INVALID_DISPLAY;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
+import static com.android.server.pm.UserManagerInternal.USER_START_MODE_BACKGROUND;
+import static com.android.server.pm.UserManagerInternal.USER_START_MODE_BACKGROUND_VISIBLE;
+import static com.android.server.pm.UserManagerInternal.USER_START_MODE_FOREGROUND;
import static com.android.server.pm.UserManagerInternal.userAssignmentResultToString;
import static com.android.server.pm.UserVisibilityChangedEvent.onInvisible;
import static com.android.server.pm.UserVisibilityChangedEvent.onVisible;
@@ -99,8 +102,9 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase {
*/
protected static final int OTHER_SECONDARY_DISPLAY_ID = 108;
- protected static final boolean FG = true;
- protected static final boolean BG = false;
+ protected static final int FG = USER_START_MODE_FOREGROUND;
+ protected static final int BG = USER_START_MODE_BACKGROUND;
+ protected static final int BG_VISIBLE = USER_START_MODE_BACKGROUND_VISIBLE;
private Handler mHandler;
protected AsyncUserVisibilityListener.Factory mListenerFactory;
@@ -154,8 +158,7 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase {
public final void testStartBgUser_onDefaultDisplay() throws Exception {
AsyncUserVisibilityListener listener = addListenerForNoEvents();
- int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG,
- DEFAULT_DISPLAY);
+ int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG, DEFAULT_DISPLAY);
assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
expectUserIsNotVisibleAtAll(USER_ID);
@@ -166,6 +169,34 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase {
}
@Test
+ public final void testStartBgUser_onDefaultDisplay_visible() throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForNoEvents();
+
+ int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
+ DEFAULT_DISPLAY);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+ expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+ expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+
+ listener.verify();
+ }
+
+ @Test
+ public final void testStartBgUser_onSecondaryDisplay_invisible() throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForNoEvents();
+
+ int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG,
+ SECONDARY_DISPLAY_ID);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+ expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+ expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+
+ listener.verify();
+ }
+
+ @Test
public final void testStartBgSystemUser_onSecondaryDisplay() throws Exception {
AsyncUserVisibilityListener listener = addListenerForEvents(
onInvisible(INITIAL_CURRENT_USER_ID),
@@ -228,8 +259,8 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase {
throws Exception {
AsyncUserVisibilityListener listener = addListenerForNoEvents();
- int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
- DEFAULT_DISPLAY);
+ int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
+ BG_VISIBLE, DEFAULT_DISPLAY);
assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
@@ -244,8 +275,8 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase {
AsyncUserVisibilityListener listener = addListenerForNoEvents();
startBackgroundUser(PARENT_USER_ID);
- int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
- DEFAULT_DISPLAY);
+ int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
+ BG_VISIBLE, DEFAULT_DISPLAY);
assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
@@ -261,6 +292,21 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase {
public final void testStartBgProfile_onSecondaryDisplay() throws Exception {
AsyncUserVisibilityListener listener = addListenerForNoEvents();
+ int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
+ BG_VISIBLE, SECONDARY_DISPLAY_ID);
+ assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+ expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+ expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+ expectNoUserAssignedToDisplay(SECONDARY_DISPLAY_ID);
+
+ listener.verify();
+ }
+
+ @Test
+ public final void testStartBgProfile_onSecondaryDisplay_invisible() throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForNoEvents();
+
int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
SECONDARY_DISPLAY_ID);
assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
@@ -384,8 +430,8 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase {
Log.d(TAG, "starting default profile (" + PROFILE_USER_ID + ") in background after starting"
+ " its parent (" + PARENT_USER_ID + ") on foreground");
- int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
- DEFAULT_DISPLAY);
+ int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
+ BG_VISIBLE, DEFAULT_DISPLAY);
if (result != USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE) {
throw new IllegalStateException("Failed to start profile user " + PROFILE_USER_ID
+ ": mediator returned " + userAssignmentResultToString(result));
@@ -403,7 +449,7 @@ abstract class UserVisibilityMediatorTestCase extends ExtendedMockitoTestCase {
Preconditions.checkArgument(displayId != INVALID_DISPLAY && displayId != DEFAULT_DISPLAY,
"must pass a secondary display, not %d", displayId);
Log.d(TAG, "startUserInSecondaryDisplay(" + userId + ", " + displayId + ")");
- int result = mMediator.assignUserToDisplayOnStart(userId, userId, BG, displayId);
+ int result = mMediator.assignUserToDisplayOnStart(userId, userId, BG_VISIBLE, displayId);
if (result != USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE) {
throw new IllegalStateException("Failed to startuser " + userId
+ " on background: mediator returned " + userAssignmentResultToString(result));
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 98037d792bf4..0dfe664432c3 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -243,14 +243,6 @@ public class UserControllerTest {
}
@Test
- public void testStartUserOnSecondaryDisplay_defaultDisplay() {
- assertThrows(IllegalArgumentException.class, () -> mUserController
- .startUserOnSecondaryDisplay(TEST_USER_ID, Display.DEFAULT_DISPLAY));
-
- verifyUserNeverAssignedToDisplay();
- }
-
- @Test
public void testStartUserOnSecondaryDisplay() {
boolean started = mUserController.startUserOnSecondaryDisplay(TEST_USER_ID, 42);
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
index d2f2af1b91b6..9c7c574f8e31 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
@@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.startsWith;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -143,4 +144,38 @@ public class InputControllerTest {
verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
}
+ @Test
+ public void createNavigationTouchpad_hasDeviceId() {
+ final IBinder deviceToken = new Binder();
+ mInputController.createNavigationTouchpad("name", /*vendorId= */ 1, /*productId= */ 1,
+ deviceToken, /* displayId= */ 1, /* touchpadHeight= */ 50, /* touchpadWidth= */ 50);
+
+ int deviceId = mInputController.getInputDeviceId(deviceToken);
+ int[] deviceIds = InputManager.getInstance().getInputDeviceIds();
+
+ assertWithMessage("InputManager's deviceIds list should contain id of the device").that(
+ deviceIds).asList().contains(deviceId);
+ }
+
+ @Test
+ public void createNavigationTouchpad_setsTypeAssociation() {
+ final IBinder deviceToken = new Binder();
+ mInputController.createNavigationTouchpad("name", /*vendorId= */ 1, /*productId= */ 1,
+ deviceToken, /* displayId= */ 1, /* touchpadHeight= */ 50, /* touchpadWidth= */ 50);
+
+ verify(mInputManagerInternalMock).setTypeAssociation(
+ startsWith("virtualNavigationTouchpad:"), eq("touchNavigation"));
+ }
+
+ @Test
+ public void createAndUnregisterNavigationTouchpad_unsetsTypeAssociation() {
+ final IBinder deviceToken = new Binder();
+ mInputController.createNavigationTouchpad("name", /*vendorId= */ 1, /*productId= */ 1,
+ deviceToken, /* displayId= */ 1, /* touchpadHeight= */ 50, /* touchpadWidth= */ 50);
+
+ mInputController.unregisterInputDevice(deviceToken);
+
+ verify(mInputManagerInternalMock).unsetTypeAssociation(
+ startsWith("virtualNavigationTouchpad:"));
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 5a3d7b580748..31e53d56f520 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -69,6 +69,7 @@ import android.hardware.input.VirtualMouseButtonEvent;
import android.hardware.input.VirtualMouseConfig;
import android.hardware.input.VirtualMouseRelativeEvent;
import android.hardware.input.VirtualMouseScrollEvent;
+import android.hardware.input.VirtualNavigationTouchpadConfig;
import android.hardware.input.VirtualTouchEvent;
import android.hardware.input.VirtualTouchscreenConfig;
import android.net.MacAddress;
@@ -134,8 +135,6 @@ public class VirtualDeviceManagerServiceTest {
private static final int UID_2 = 10;
private static final int UID_3 = 10000;
private static final int UID_4 = 10001;
- private static final int ASSOCIATION_ID_1 = 1;
- private static final int ASSOCIATION_ID_2 = 2;
private static final int PRODUCT_ID = 10;
private static final int VENDOR_ID = 5;
private static final String UNIQUE_ID = "uniqueid";
@@ -177,6 +176,14 @@ public class VirtualDeviceManagerServiceTest {
.setWidthInPixels(WIDTH)
.setHeightInPixels(HEIGHT)
.build();
+ private static final VirtualNavigationTouchpadConfig NAVIGATION_TOUCHPAD_CONFIG =
+ new VirtualNavigationTouchpadConfig.Builder(
+ /* touchpadHeight= */ HEIGHT, /* touchpadWidth= */ WIDTH)
+ .setVendorId(VENDOR_ID)
+ .setProductId(PRODUCT_ID)
+ .setInputDeviceName(DEVICE_NAME)
+ .setAssociatedDisplayId(DISPLAY_ID)
+ .build();
private Context mContext;
private InputManagerMockHelper mInputManagerMockHelper;
@@ -307,13 +314,13 @@ public class VirtualDeviceManagerServiceTest {
mContext.getSystemService(WindowManager.class), threadVerifier);
mSensorController = new SensorController(new Object(), VIRTUAL_DEVICE_ID);
- mAssociationInfo = new AssociationInfo(ASSOCIATION_ID_1, 0, null,
+ mAssociationInfo = new AssociationInfo(/* associationId= */ 1, 0, null,
MacAddress.BROADCAST_ADDRESS, "", null, null, true, false, false, 0, 0);
mVdms = new VirtualDeviceManagerService(mContext);
mLocalService = mVdms.getLocalServiceInstance();
mVdm = mVdms.new VirtualDeviceManagerImpl();
- mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID, DEVICE_OWNER_UID_1, mAssociationInfo);
+ mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID, DEVICE_OWNER_UID_1);
}
@Test
@@ -377,7 +384,8 @@ public class VirtualDeviceManagerServiceTest {
.build();
mDeviceImpl = new VirtualDeviceImpl(mContext,
mAssociationInfo, new Binder(), /* ownerUid */ 0, VIRTUAL_DEVICE_ID,
- mInputController, mSensorController, (int associationId) -> {},
+ mInputController, mSensorController,
+ /* onDeviceCloseListener= */ (int deviceId) -> {},
mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params);
mVdms.addVirtualDevice(mDeviceImpl);
@@ -396,9 +404,7 @@ public class VirtualDeviceManagerServiceTest {
int firstDeviceId = mDeviceImpl.getDeviceId();
int secondDeviceId = VIRTUAL_DEVICE_ID + 1;
- createVirtualDevice(secondDeviceId, DEVICE_OWNER_UID_2,
- new AssociationInfo(ASSOCIATION_ID_2, 0, null,
- MacAddress.BROADCAST_ADDRESS, "", null, null, true, false, false, 0, 0));
+ createVirtualDevice(secondDeviceId, DEVICE_OWNER_UID_2);
int secondDeviceOwner = mLocalService.getDeviceOwnerUid(secondDeviceId);
assertThat(secondDeviceOwner).isEqualTo(DEVICE_OWNER_UID_2);
@@ -457,9 +463,7 @@ public class VirtualDeviceManagerServiceTest {
public void getDeviceIdsForUid_twoDevicesUidOnOne_returnsCorrectId() {
int secondDeviceId = VIRTUAL_DEVICE_ID + 1;
- VirtualDeviceImpl secondDevice = createVirtualDevice(secondDeviceId, DEVICE_OWNER_UID_2,
- new AssociationInfo(ASSOCIATION_ID_2, 0, null,
- MacAddress.BROADCAST_ADDRESS, "", null, null, true, false, false, 0, 0));
+ VirtualDeviceImpl secondDevice = createVirtualDevice(secondDeviceId, DEVICE_OWNER_UID_2);
GenericWindowPolicyController gwpc =
secondDevice.createWindowPolicyController(new ArrayList<>());
@@ -474,9 +478,7 @@ public class VirtualDeviceManagerServiceTest {
public void getDeviceIdsForUid_twoDevicesUidOnBoth_returnsCorrectId() {
int secondDeviceId = VIRTUAL_DEVICE_ID + 1;
- VirtualDeviceImpl secondDevice = createVirtualDevice(secondDeviceId, DEVICE_OWNER_UID_2,
- new AssociationInfo(ASSOCIATION_ID_2, 0, null,
- MacAddress.BROADCAST_ADDRESS, "", null, null, true, false, false, 0, 0));
+ VirtualDeviceImpl secondDevice = createVirtualDevice(secondDeviceId, DEVICE_OWNER_UID_2);
GenericWindowPolicyController gwpc1 =
mDeviceImpl.createWindowPolicyController(new ArrayList<>());
GenericWindowPolicyController gwpc2 =
@@ -526,7 +528,7 @@ public class VirtualDeviceManagerServiceTest {
ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2));
mLocalService.registerAppsOnVirtualDeviceListener(mAppsOnVirtualDeviceListener);
- mVdms.notifyRunningAppsChanged(ASSOCIATION_ID_1, uids);
+ mVdms.notifyRunningAppsChanged(mDeviceImpl.getDeviceId(), uids);
TestableLooper.get(this).processAllMessages();
verify(mAppsOnVirtualDeviceListener).onAppsOnAnyVirtualDeviceChanged(uids);
@@ -539,13 +541,13 @@ public class VirtualDeviceManagerServiceTest {
mLocalService.registerAppsOnVirtualDeviceListener(mAppsOnVirtualDeviceListener);
// Notifies that the running apps on the first virtual device has changed.
- mVdms.notifyRunningAppsChanged(ASSOCIATION_ID_1, uidsOnDevice1);
+ mVdms.notifyRunningAppsChanged(mDeviceImpl.getDeviceId(), uidsOnDevice1);
TestableLooper.get(this).processAllMessages();
verify(mAppsOnVirtualDeviceListener).onAppsOnAnyVirtualDeviceChanged(
new ArraySet<>(Arrays.asList(UID_1, UID_2)));
// Notifies that the running apps on the second virtual device has changed.
- mVdms.notifyRunningAppsChanged(ASSOCIATION_ID_2, uidsOnDevice2);
+ mVdms.notifyRunningAppsChanged(mDeviceImpl.getDeviceId() + 1, uidsOnDevice2);
TestableLooper.get(this).processAllMessages();
// The union of the apps running on both virtual devices are sent to the listeners.
verify(mAppsOnVirtualDeviceListener).onAppsOnAnyVirtualDeviceChanged(
@@ -553,7 +555,7 @@ public class VirtualDeviceManagerServiceTest {
// Notifies that the running apps on the first virtual device has changed again.
uidsOnDevice1.remove(UID_2);
- mVdms.notifyRunningAppsChanged(ASSOCIATION_ID_1, uidsOnDevice1);
+ mVdms.notifyRunningAppsChanged(mDeviceImpl.getDeviceId(), uidsOnDevice1);
mLocalService.onAppsOnVirtualDeviceChanged();
TestableLooper.get(this).processAllMessages();
// The union of the apps running on both virtual devices are sent to the listeners.
@@ -562,7 +564,7 @@ public class VirtualDeviceManagerServiceTest {
// Notifies that the running apps on the first virtual device has changed but with the same
// set of UIDs.
- mVdms.notifyRunningAppsChanged(ASSOCIATION_ID_1, uidsOnDevice1);
+ mVdms.notifyRunningAppsChanged(mDeviceImpl.getDeviceId(), uidsOnDevice1);
mLocalService.onAppsOnVirtualDeviceChanged();
TestableLooper.get(this).processAllMessages();
// Listeners should not be notified.
@@ -707,8 +709,67 @@ public class VirtualDeviceManagerServiceTest {
.build();
mDeviceImpl.createVirtualTouchscreen(positiveConfig, BINDER);
assertWithMessage(
- "Virtual touchscreen should create input device descriptor on successful creation"
- + ".").that(mInputController.getInputDeviceDescriptors()).isNotEmpty();
+ "Virtual touchscreen should create input device descriptor on successful creation"
+ + ".").that(mInputController.getInputDeviceDescriptors()).isNotEmpty();
+ }
+
+ @Test
+ public void createVirtualNavigationTouchpad_noDisplay_failsSecurityException() {
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualNavigationTouchpad(NAVIGATION_TOUCHPAD_CONFIG,
+ BINDER));
+ }
+
+ @Test
+ public void createVirtualNavigationTouchpad_zeroDisplayDimension_failsWithException() {
+ mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+ assertThrows(IllegalArgumentException.class,
+ () -> {
+ final VirtualNavigationTouchpadConfig zeroConfig =
+ new VirtualNavigationTouchpadConfig.Builder(
+ /* touchpadHeight= */ 0, /* touchpadWidth= */ 0)
+ .setVendorId(VENDOR_ID)
+ .setProductId(PRODUCT_ID)
+ .setInputDeviceName(DEVICE_NAME)
+ .setAssociatedDisplayId(DISPLAY_ID)
+ .build();
+ mDeviceImpl.createVirtualNavigationTouchpad(zeroConfig, BINDER);
+ });
+ }
+
+ @Test
+ public void createVirtualNavigationTouchpad_negativeDisplayDimension_failsWithException() {
+ mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+ assertThrows(IllegalArgumentException.class,
+ () -> {
+ final VirtualNavigationTouchpadConfig zeroConfig =
+ new VirtualNavigationTouchpadConfig.Builder(
+ /* touchpadHeight= */ -50, /* touchpadWidth= */ 50)
+ .setVendorId(VENDOR_ID)
+ .setProductId(PRODUCT_ID)
+ .setInputDeviceName(DEVICE_NAME)
+ .setAssociatedDisplayId(DISPLAY_ID)
+ .build();
+ mDeviceImpl.createVirtualNavigationTouchpad(zeroConfig, BINDER);
+ });
+ }
+
+ @Test
+ public void createVirtualNavigationTouchpad_positiveDisplayDimension_successful() {
+ mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+ VirtualNavigationTouchpadConfig positiveConfig =
+ new VirtualNavigationTouchpadConfig.Builder(
+ /* touchpadHeight= */ 50, /* touchpadWidth= */ 50)
+ .setVendorId(VENDOR_ID)
+ .setProductId(PRODUCT_ID)
+ .setInputDeviceName(DEVICE_NAME)
+ .setAssociatedDisplayId(DISPLAY_ID)
+ .build();
+ mDeviceImpl.createVirtualNavigationTouchpad(positiveConfig, BINDER);
+ assertWithMessage(
+ "Virtual navigation touchpad should create input device descriptor on successful "
+ + "creation"
+ + ".").that(mInputController.getInputDeviceDescriptors()).isNotEmpty();
}
@Test
@@ -755,6 +816,16 @@ public class VirtualDeviceManagerServiceTest {
}
@Test
+ public void createVirtualNavigationTouchpad_noPermission_failsSecurityException() {
+ mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+ doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
+ eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualNavigationTouchpad(NAVIGATION_TOUCHPAD_CONFIG,
+ BINDER));
+ }
+
+ @Test
public void createVirtualSensor_noPermission_failsSecurityException() {
doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
@@ -818,7 +889,18 @@ public class VirtualDeviceManagerServiceTest {
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER);
assertWithMessage("Virtual touchscreen should register fd when the display matches").that(
- mInputController.getInputDeviceDescriptors()).isNotEmpty();
+ mInputController.getInputDeviceDescriptors()).isNotEmpty();
+ verify(mNativeWrapperMock).openUinputTouchscreen(eq(DEVICE_NAME), eq(VENDOR_ID),
+ eq(PRODUCT_ID), anyString(), eq(HEIGHT), eq(WIDTH));
+ }
+
+ @Test
+ public void createVirtualNavigationTouchpad_hasDisplay_obtainFileDescriptor() {
+ mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+ mDeviceImpl.createVirtualNavigationTouchpad(NAVIGATION_TOUCHPAD_CONFIG, BINDER);
+ assertWithMessage("Virtual navigation touchpad should register fd when the display matches")
+ .that(
+ mInputController.getInputDeviceDescriptors()).isNotEmpty();
verify(mNativeWrapperMock).openUinputTouchscreen(eq(DEVICE_NAME), eq(VENDOR_ID),
eq(PRODUCT_ID), anyString(), eq(HEIGHT), eq(WIDTH));
}
@@ -1288,18 +1370,17 @@ public class VirtualDeviceManagerServiceTest {
intent.filterEquals(blockedAppIntent)), any(), any());
}
- private VirtualDeviceImpl createVirtualDevice(int virtualDeviceId, int ownerUid,
- AssociationInfo associationInfo) {
+ private VirtualDeviceImpl createVirtualDevice(int virtualDeviceId, int ownerUid) {
VirtualDeviceParams params = new VirtualDeviceParams
.Builder()
.setBlockedActivities(getBlockedActivities())
.build();
VirtualDeviceImpl virtualDeviceImpl = new VirtualDeviceImpl(mContext,
- associationInfo, new Binder(), ownerUid, virtualDeviceId,
- mInputController, mSensorController, (int associationId) -> {},
+ mAssociationInfo, new Binder(), ownerUid, virtualDeviceId,
+ mInputController, mSensorController,
+ /* onDeviceCloseListener= */ (int deviceId) -> {},
mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params);
mVdms.addVirtualDevice(virtualDeviceImpl);
return virtualDeviceImpl;
}
-
}
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
index c7caa4372b0e..c6a0b0f936a5 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -18,10 +18,14 @@ package com.android.server.display;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.DEFAULT_DISPLAY_GROUP;
+import static android.view.Display.TYPE_INTERNAL;
+import static android.view.Display.TYPE_VIRTUAL;
+import static com.android.server.display.DeviceStateToLayoutMap.STATE_DEFAULT;
import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED;
import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED;
import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED;
+import static com.android.server.display.DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY;
import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED;
import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED;
@@ -69,7 +73,6 @@ import org.mockito.Spy;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
-import java.util.Set;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -155,8 +158,8 @@ public class LogicalDisplayMapperTest {
@Test
public void testDisplayDeviceAddAndRemove_Internal() {
- DisplayDevice device = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ DisplayDevice device = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
// add
LogicalDisplay displayAdded = add(device);
@@ -177,7 +180,7 @@ public class LogicalDisplayMapperTest {
testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_EXTERNAL);
testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_WIFI);
testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_OVERLAY);
- testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_VIRTUAL);
+ testDisplayDeviceAddAndRemove_NonInternal(TYPE_VIRTUAL);
testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_UNKNOWN);
// Call the internal test again, just to verify that adding non-internal displays
@@ -187,9 +190,9 @@ public class LogicalDisplayMapperTest {
@Test
public void testDisplayDeviceAdd_TwoInternalOneDefault() {
- DisplayDevice device1 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, 0);
- DisplayDevice device2 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800, 0);
+ DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
LogicalDisplay display1 = add(device1);
assertEquals(info(display1).address, info(device1).address);
@@ -202,10 +205,10 @@ public class LogicalDisplayMapperTest {
@Test
public void testDisplayDeviceAdd_TwoInternalBothDefault() {
- DisplayDevice device1 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
- DisplayDevice device2 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
LogicalDisplay display1 = add(device1);
assertEquals(info(display1).address, info(device1).address);
@@ -220,7 +223,7 @@ public class LogicalDisplayMapperTest {
@Test
public void testDisplayDeviceAddAndRemove_OneExternalDefault() {
DisplayDevice device = createDisplayDevice(Display.TYPE_EXTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
// add
LogicalDisplay displayAdded = add(device);
@@ -238,10 +241,10 @@ public class LogicalDisplayMapperTest {
@Test
public void testDisplayDeviceAddAndRemove_SwitchDefault() {
- DisplayDevice device1 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
- DisplayDevice device2 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
LogicalDisplay display1 = add(device1);
assertEquals(info(display1).address, info(device1).address);
@@ -267,10 +270,10 @@ public class LogicalDisplayMapperTest {
@Test
public void testGetDisplayIdsLocked() {
- add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
+ add(createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
add(createDisplayDevice(Display.TYPE_EXTERNAL, 600, 800, 0));
- add(createDisplayDevice(Display.TYPE_VIRTUAL, 600, 800, 0));
+ add(createDisplayDevice(TYPE_VIRTUAL, 600, 800, 0));
int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID,
/* includeDisabled= */ true);
@@ -280,71 +283,98 @@ public class LogicalDisplayMapperTest {
}
@Test
- public void testGetDisplayInfoForStateLocked_oneDisplayGroup_internalType() {
- add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
- add(createDisplayDevice(Display.TYPE_INTERNAL, 200, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
- add(createDisplayDevice(Display.TYPE_INTERNAL, 700, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
-
- Set<DisplayInfo> displayInfos = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
- DeviceStateToLayoutMap.STATE_DEFAULT, DEFAULT_DISPLAY, DEFAULT_DISPLAY_GROUP);
- assertThat(displayInfos.size()).isEqualTo(1);
- for (DisplayInfo displayInfo : displayInfos) {
- assertThat(displayInfo.displayId).isEqualTo(DEFAULT_DISPLAY);
- assertThat(displayInfo.displayGroupId).isEqualTo(DEFAULT_DISPLAY_GROUP);
- assertThat(displayInfo.logicalWidth).isEqualTo(600);
- assertThat(displayInfo.logicalHeight).isEqualTo(800);
- }
+ public void testGetDisplayInfoForStateLocked_defaultLayout() {
+ final DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ final DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 200, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+
+ add(device1);
+ add(device2);
+
+ Layout layout1 = new Layout();
+ layout1.createDisplayLocked(info(device1).address, /* isDefault= */ true,
+ /* isEnabled= */ true, mIdProducer);
+ layout1.createDisplayLocked(info(device2).address, /* isDefault= */ false,
+ /* isEnabled= */ true, mIdProducer);
+ when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT)).thenReturn(layout1);
+ assertThat(layout1.size()).isEqualTo(2);
+ final int logicalId2 = layout1.getByAddress(info(device2).address).getLogicalDisplayId();
+
+ final DisplayInfo displayInfoDefault = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+ STATE_DEFAULT, DEFAULT_DISPLAY);
+ assertThat(displayInfoDefault.displayId).isEqualTo(DEFAULT_DISPLAY);
+ assertThat(displayInfoDefault.logicalWidth).isEqualTo(width(device1));
+ assertThat(displayInfoDefault.logicalHeight).isEqualTo(height(device1));
+
+ final DisplayInfo displayInfoOther = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+ STATE_DEFAULT, logicalId2);
+ assertThat(displayInfoOther).isNotNull();
+ assertThat(displayInfoOther.displayId).isEqualTo(logicalId2);
+ assertThat(displayInfoOther.logicalWidth).isEqualTo(width(device2));
+ assertThat(displayInfoOther.logicalHeight).isEqualTo(height(device2));
}
@Test
- public void testGetDisplayInfoForStateLocked_oneDisplayGroup_differentTypes() {
- add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
- add(createDisplayDevice(Display.TYPE_INTERNAL, 200, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
- add(createDisplayDevice(Display.TYPE_EXTERNAL, 700, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
-
- Set<DisplayInfo> displayInfos = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
- DeviceStateToLayoutMap.STATE_DEFAULT, DEFAULT_DISPLAY, DEFAULT_DISPLAY_GROUP);
- assertThat(displayInfos.size()).isEqualTo(1);
- for (DisplayInfo displayInfo : displayInfos) {
- assertThat(displayInfo.displayId).isEqualTo(DEFAULT_DISPLAY);
- assertThat(displayInfo.displayGroupId).isEqualTo(DEFAULT_DISPLAY_GROUP);
- assertThat(displayInfo.logicalWidth).isEqualTo(600);
- assertThat(displayInfo.logicalHeight).isEqualTo(800);
- }
- }
+ public void testGetDisplayInfoForStateLocked_multipleLayouts() {
+ final DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ final DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 200, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ final DisplayDevice device3 = createDisplayDevice(TYPE_VIRTUAL, 700, 800,
+ DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
- @Test
- public void testGetDisplayInfoForStateLocked_multipleDisplayGroups_defaultGroup() {
- add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
- add(createDisplayDevice(Display.TYPE_INTERNAL, 200, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
- add(createDisplayDevice(Display.TYPE_VIRTUAL, 700, 800,
- DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP));
-
- Set<DisplayInfo> displayInfos = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
- DeviceStateToLayoutMap.STATE_DEFAULT, DEFAULT_DISPLAY, DEFAULT_DISPLAY_GROUP);
- assertThat(displayInfos.size()).isEqualTo(1);
- for (DisplayInfo displayInfo : displayInfos) {
- assertThat(displayInfo.displayId).isEqualTo(DEFAULT_DISPLAY);
- assertThat(displayInfo.displayGroupId).isEqualTo(DEFAULT_DISPLAY_GROUP);
- assertThat(displayInfo.logicalWidth).isEqualTo(600);
- assertThat(displayInfo.logicalHeight).isEqualTo(800);
- }
+ add(device1);
+ add(device2);
+ add(device3);
+
+ Layout layout1 = new Layout();
+ layout1.createDisplayLocked(info(device1).address,
+ /* isDefault= */ true, /* isEnabled= */ true, mIdProducer);
+ when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT)).thenReturn(layout1);
+
+ final int layoutState2 = 2;
+ Layout layout2 = new Layout();
+ layout2.createDisplayLocked(info(device2).address,
+ /* isDefault= */ false, /* isEnabled= */ true, mIdProducer);
+ // Device3 is the default display.
+ layout2.createDisplayLocked(info(device3).address,
+ /* isDefault= */ true, /* isEnabled= */ true, mIdProducer);
+ when(mDeviceStateToLayoutMapSpy.get(layoutState2)).thenReturn(layout2);
+ assertThat(layout2.size()).isEqualTo(2);
+ final int logicalId2 = layout2.getByAddress(info(device2).address).getLogicalDisplayId();
+
+ // Default layout.
+ final DisplayInfo displayInfoLayout1Default =
+ mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+ STATE_DEFAULT, DEFAULT_DISPLAY);
+ assertThat(displayInfoLayout1Default.displayId).isEqualTo(DEFAULT_DISPLAY);
+ assertThat(displayInfoLayout1Default.logicalWidth).isEqualTo(width(device1));
+ assertThat(displayInfoLayout1Default.logicalHeight).isEqualTo(height(device1));
+
+ // Second layout, where device3 is the default display.
+ final DisplayInfo displayInfoLayout2Default =
+ mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+ layoutState2, DEFAULT_DISPLAY);
+ assertThat(displayInfoLayout2Default.displayId).isEqualTo(DEFAULT_DISPLAY);
+ assertThat(displayInfoLayout2Default.logicalWidth).isEqualTo(width(device3));
+ assertThat(displayInfoLayout2Default.logicalHeight).isEqualTo(height(device3));
+
+ final DisplayInfo displayInfoLayout2Other =
+ mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+ layoutState2, logicalId2);
+ assertThat(displayInfoLayout2Other).isNotNull();
+ assertThat(displayInfoLayout2Other.displayId).isEqualTo(logicalId2);
+ assertThat(displayInfoLayout2Other.logicalWidth).isEqualTo(width(device2));
+ assertThat(displayInfoLayout2Other.logicalHeight).isEqualTo(height(device2));
}
@Test
public void testSingleDisplayGroup() {
- LogicalDisplay display1 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
- LogicalDisplay display2 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, 0));
- LogicalDisplay display3 = add(createDisplayDevice(Display.TYPE_VIRTUAL, 600, 800, 0));
+ LogicalDisplay display1 = add(createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
+ LogicalDisplay display2 = add(createDisplayDevice(TYPE_INTERNAL, 600, 800, 0));
+ LogicalDisplay display3 = add(createDisplayDevice(TYPE_VIRTUAL, 600, 800, 0));
assertEquals(DEFAULT_DISPLAY_GROUP,
mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display1)));
@@ -356,12 +386,12 @@ public class LogicalDisplayMapperTest {
@Test
public void testMultipleDisplayGroups() {
- LogicalDisplay display1 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
- LogicalDisplay display2 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, 0));
+ LogicalDisplay display1 = add(createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
+ LogicalDisplay display2 = add(createDisplayDevice(TYPE_INTERNAL, 600, 800, 0));
- TestDisplayDevice device3 = createDisplayDevice(Display.TYPE_VIRTUAL, 600, 800,
+ TestDisplayDevice device3 = createDisplayDevice(TYPE_VIRTUAL, 600, 800,
DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
LogicalDisplay display3 = add(device3);
@@ -519,10 +549,10 @@ public class LogicalDisplayMapperTest {
@Test
public void testDeviceStateLocked() {
- DisplayDevice device1 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
- DisplayDevice device2 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+ FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
Layout layout = new Layout();
layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
@@ -577,14 +607,11 @@ public class LogicalDisplayMapperTest {
DisplayAddress displayAddressThree = new TestUtils.TestDisplayAddress();
TestDisplayDevice device1 = createDisplayDevice(displayAddressOne, "one",
- Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+ TYPE_INTERNAL, 600, 800, DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
TestDisplayDevice device2 = createDisplayDevice(displayAddressTwo, "two",
- Display.TYPE_INTERNAL, 200, 800,
- DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
+ TYPE_INTERNAL, 200, 800, DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
TestDisplayDevice device3 = createDisplayDevice(displayAddressThree, "three",
- Display.TYPE_INTERNAL, 600, 900,
- DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
+ TYPE_INTERNAL, 600, 900, DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
Layout threeDevicesEnabledLayout = new Layout();
threeDevicesEnabledLayout.createDisplayLocked(
@@ -603,7 +630,7 @@ public class LogicalDisplayMapperTest {
/* isEnabled= */ true,
mIdProducer);
- when(mDeviceStateToLayoutMapSpy.get(DeviceStateToLayoutMap.STATE_DEFAULT))
+ when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT))
.thenReturn(threeDevicesEnabledLayout);
LogicalDisplay display1 = add(device1);
@@ -735,6 +762,14 @@ public class LogicalDisplayMapperTest {
return device.getDisplayDeviceInfoLocked();
}
+ private int width(DisplayDevice device) {
+ return info(device).width;
+ }
+
+ private int height(DisplayDevice device) {
+ return info(device).height;
+ }
+
private DisplayInfo info(LogicalDisplay display) {
return display.getDisplayInfoLocked();
}
diff --git a/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt b/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt
index e390bccf41d8..3326f80f5f91 100644
--- a/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt
@@ -16,6 +16,7 @@
package com.android.server.input
+
import android.content.Context
import android.content.ContextWrapper
import android.hardware.display.DisplayViewport
@@ -25,6 +26,7 @@ import android.platform.test.annotations.Presubmit
import android.view.Display
import android.view.PointerIcon
import androidx.test.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import org.junit.Assert.assertFalse
@@ -287,6 +289,28 @@ class InputManagerServiceTests {
verify(native).setPointerAcceleration(eq(5f))
}
+ @Test
+ fun setDeviceTypeAssociation_setsDeviceTypeAssociation() {
+ val inputPort = "inputPort"
+ val type = "type"
+
+ localService.setTypeAssociation(inputPort, type)
+
+ assertThat(service.getDeviceTypeAssociations()).asList().containsExactly(inputPort, type)
+ .inOrder()
+ }
+
+ @Test
+ fun setAndUnsetDeviceTypeAssociation_deviceTypeAssociationIsMissing() {
+ val inputPort = "inputPort"
+ val type = "type"
+
+ localService.setTypeAssociation(inputPort, type)
+ localService.unsetTypeAssociation(inputPort)
+
+ assertTrue(service.getDeviceTypeAssociations().isEmpty())
+ }
+
private fun setVirtualMousePointerDisplayIdAndVerify(overrideDisplayId: Int) {
val thread = Thread { localService.setVirtualMousePointerDisplayId(overrideDisplayId) }
thread.start()
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index 196226a220a7..41e3a08be6c5 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -59,65 +59,65 @@ import org.junit.runner.RunWith;
@Presubmit
@RunWith(AndroidJUnit4.class)
public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
+
@Before
- public void disableProcessCaches() {
+ public void setUp() {
PropertyInvalidatedCache.disableForTestMode();
+ mService.initializeSyntheticPassword(PRIMARY_USER_ID);
+ mService.initializeSyntheticPassword(MANAGED_PROFILE_USER_ID);
}
@Test
- public void testCreatePasswordPrimaryUser() throws RemoteException {
- testCreateCredential(PRIMARY_USER_ID, newPassword("password"));
+ public void testSetPasswordPrimaryUser() throws RemoteException {
+ setAndVerifyCredential(PRIMARY_USER_ID, newPassword("password"));
}
@Test
- public void testCreatePasswordFailsWithoutLockScreen() throws RemoteException {
- testCreateCredentialFailsWithoutLockScreen(PRIMARY_USER_ID, newPassword("password"));
+ public void testSetPasswordFailsWithoutLockScreen() throws RemoteException {
+ testSetCredentialFailsWithoutLockScreen(PRIMARY_USER_ID, newPassword("password"));
}
@Test
- public void testCreatePatternPrimaryUser() throws RemoteException {
- testCreateCredential(PRIMARY_USER_ID, newPattern("123456789"));
+ public void testSetPatternPrimaryUser() throws RemoteException {
+ setAndVerifyCredential(PRIMARY_USER_ID, newPattern("123456789"));
}
@Test
- public void testCreatePatternFailsWithoutLockScreen() throws RemoteException {
- testCreateCredentialFailsWithoutLockScreen(PRIMARY_USER_ID, newPattern("123456789"));
+ public void testSetPatternFailsWithoutLockScreen() throws RemoteException {
+ testSetCredentialFailsWithoutLockScreen(PRIMARY_USER_ID, newPattern("123456789"));
}
@Test
public void testChangePasswordPrimaryUser() throws RemoteException {
- testChangeCredentials(PRIMARY_USER_ID, newPattern("78963214"), newPassword("asdfghjk"));
+ testChangeCredential(PRIMARY_USER_ID, newPattern("78963214"), newPassword("asdfghjk"));
}
@Test
public void testChangePatternPrimaryUser() throws RemoteException {
- testChangeCredentials(PRIMARY_USER_ID, newPassword("!£$%^&*(())"), newPattern("1596321"));
+ testChangeCredential(PRIMARY_USER_ID, newPassword("!£$%^&*(())"), newPattern("1596321"));
}
@Test
public void testChangePasswordFailPrimaryUser() throws RemoteException {
- initializeStorageWithCredential(PRIMARY_USER_ID, newPassword("password"));
-
+ setCredential(PRIMARY_USER_ID, newPassword("password"));
assertFalse(mService.setLockCredential(newPassword("newpwd"), newPassword("badpwd"),
PRIMARY_USER_ID));
- assertVerifyCredentials(PRIMARY_USER_ID, newPassword("password"));
+ assertVerifyCredential(PRIMARY_USER_ID, newPassword("password"));
}
@Test
public void testClearPasswordPrimaryUser() throws RemoteException {
- initializeStorageWithCredential(PRIMARY_USER_ID, newPassword("password"));
- assertTrue(mService.setLockCredential(nonePassword(), newPassword("password"),
- PRIMARY_USER_ID));
- assertEquals(CREDENTIAL_TYPE_NONE, mService.getCredentialType(PRIMARY_USER_ID));
- assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
+ setCredential(PRIMARY_USER_ID, newPassword("password"));
+ clearCredential(PRIMARY_USER_ID, newPassword("password"));
}
@Test
public void testManagedProfileUnifiedChallenge() throws RemoteException {
+ mService.initializeSyntheticPassword(TURNED_OFF_PROFILE_USER_ID);
+
final LockscreenCredential firstUnifiedPassword = newPassword("pwd-1");
final LockscreenCredential secondUnifiedPassword = newPassword("pwd-2");
- assertTrue(mService.setLockCredential(firstUnifiedPassword,
- nonePassword(), PRIMARY_USER_ID));
+ setCredential(PRIMARY_USER_ID, firstUnifiedPassword);
mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
@@ -146,14 +146,12 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
assertNull(mGateKeeperService.getAuthToken(TURNED_OFF_PROFILE_USER_ID));
// Change primary password and verify that profile SID remains
- assertTrue(mService.setLockCredential(
- secondUnifiedPassword, firstUnifiedPassword, PRIMARY_USER_ID));
+ setCredential(PRIMARY_USER_ID, secondUnifiedPassword, firstUnifiedPassword);
assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
assertNull(mGateKeeperService.getAuthToken(TURNED_OFF_PROFILE_USER_ID));
// Clear unified challenge
- assertTrue(mService.setLockCredential(nonePassword(),
- secondUnifiedPassword, PRIMARY_USER_ID));
+ clearCredential(PRIMARY_USER_ID, secondUnifiedPassword);
assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
assertEquals(0, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
assertEquals(0, mGateKeeperService.getSecureUserId(TURNED_OFF_PROFILE_USER_ID));
@@ -163,12 +161,8 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
public void testManagedProfileSeparateChallenge() throws RemoteException {
final LockscreenCredential primaryPassword = newPassword("primary");
final LockscreenCredential profilePassword = newPassword("profile");
- assertTrue(mService.setLockCredential(primaryPassword,
- nonePassword(),
- PRIMARY_USER_ID));
- assertTrue(mService.setLockCredential(profilePassword,
- nonePassword(),
- MANAGED_PROFILE_USER_ID));
+ setCredential(PRIMARY_USER_ID, primaryPassword);
+ setCredential(MANAGED_PROFILE_USER_ID, profilePassword);
final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
@@ -191,8 +185,7 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
assertNotNull(mGateKeeperService.getAuthToken(MANAGED_PROFILE_USER_ID));
assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
- assertTrue(mService.setLockCredential(
- newPassword("pwd"), primaryPassword, PRIMARY_USER_ID));
+ setCredential(PRIMARY_USER_ID, newPassword("pwd"), primaryPassword);
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
profilePassword, MANAGED_PROFILE_USER_ID, 0 /* flags */)
.getResponseCode());
@@ -207,8 +200,7 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
assertEquals(CREDENTIAL_TYPE_NONE, mService.getCredentialType(MANAGED_PROFILE_USER_ID));
// Set a separate challenge on the profile
- assertTrue(mService.setLockCredential(
- newPassword("12345678"), nonePassword(), MANAGED_PROFILE_USER_ID));
+ setCredential(MANAGED_PROFILE_USER_ID, newPassword("12345678"));
assertNotEquals(0, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
assertEquals(CREDENTIAL_TYPE_PASSWORD, mService.getCredentialType(MANAGED_PROFILE_USER_ID));
@@ -221,11 +213,7 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
@Test
public void testSetLockCredential_forPrimaryUser_sendsCredentials() throws Exception {
- assertTrue(mService.setLockCredential(
- newPassword("password"),
- nonePassword(),
- PRIMARY_USER_ID));
-
+ setCredential(PRIMARY_USER_ID, newPassword("password"));
verify(mRecoverableKeyStoreManager)
.lockScreenSecretChanged(CREDENTIAL_TYPE_PASSWORD, "password".getBytes(),
PRIMARY_USER_ID);
@@ -234,11 +222,7 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
@Test
public void testSetLockCredential_forProfileWithSeparateChallenge_sendsCredentials()
throws Exception {
- assertTrue(mService.setLockCredential(
- newPattern("12345"),
- nonePassword(),
- MANAGED_PROFILE_USER_ID));
-
+ setCredential(MANAGED_PROFILE_USER_ID, newPattern("12345"));
verify(mRecoverableKeyStoreManager)
.lockScreenSecretChanged(CREDENTIAL_TYPE_PATTERN, "12345".getBytes(),
MANAGED_PROFILE_USER_ID);
@@ -248,13 +232,8 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
public void testSetLockCredential_forProfileWithSeparateChallenge_updatesCredentials()
throws Exception {
mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, true, null);
- initializeStorageWithCredential(MANAGED_PROFILE_USER_ID, newPattern("12345"));
-
- assertTrue(mService.setLockCredential(
- newPassword("newPassword"),
- newPattern("12345"),
- MANAGED_PROFILE_USER_ID));
-
+ setCredential(MANAGED_PROFILE_USER_ID, newPattern("12345"));
+ setCredential(MANAGED_PROFILE_USER_ID, newPassword("newPassword"), newPattern("12345"));
verify(mRecoverableKeyStoreManager)
.lockScreenSecretChanged(CREDENTIAL_TYPE_PASSWORD, "newPassword".getBytes(),
MANAGED_PROFILE_USER_ID);
@@ -264,12 +243,7 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
public void testSetLockCredential_forProfileWithUnifiedChallenge_doesNotSendRandomCredential()
throws Exception {
mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
-
- assertTrue(mService.setLockCredential(
- newPattern("12345"),
- nonePassword(),
- PRIMARY_USER_ID));
-
+ setCredential(PRIMARY_USER_ID, newPattern("12345"));
verify(mRecoverableKeyStoreManager, never())
.lockScreenSecretChanged(
eq(CREDENTIAL_TYPE_PASSWORD), any(), eq(MANAGED_PROFILE_USER_ID));
@@ -281,13 +255,9 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
throws Exception {
final LockscreenCredential oldCredential = newPassword("oldPassword");
final LockscreenCredential newCredential = newPassword("newPassword");
- initializeStorageWithCredential(PRIMARY_USER_ID, oldCredential);
+ setCredential(PRIMARY_USER_ID, oldCredential);
mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
-
- assertTrue(mService.setLockCredential(
- newCredential,
- oldCredential,
- PRIMARY_USER_ID));
+ setCredential(PRIMARY_USER_ID, newCredential, oldCredential);
verify(mRecoverableKeyStoreManager)
.lockScreenSecretChanged(CREDENTIAL_TYPE_PASSWORD, newCredential.getCredential(),
@@ -301,13 +271,9 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
public void
testSetLockCredential_forPrimaryUserWithUnifiedChallengeProfile_removesBothCredentials()
throws Exception {
- initializeStorageWithCredential(PRIMARY_USER_ID, newPassword("oldPassword"));
+ setCredential(PRIMARY_USER_ID, newPassword("oldPassword"));
mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
-
- assertTrue(mService.setLockCredential(
- nonePassword(),
- newPassword("oldPassword"),
- PRIMARY_USER_ID));
+ clearCredential(PRIMARY_USER_ID, newPassword("oldPassword"));
verify(mRecoverableKeyStoreManager)
.lockScreenSecretChanged(CREDENTIAL_TYPE_NONE, null, PRIMARY_USER_ID);
@@ -316,11 +282,10 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
}
@Test
- public void testSetLockCredential_nullCredential_removeBiometrics() throws RemoteException {
- initializeStorageWithCredential(PRIMARY_USER_ID, newPattern("123654"));
+ public void testClearLockCredential_removesBiometrics() throws RemoteException {
+ setCredential(PRIMARY_USER_ID, newPattern("123654"));
mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
-
- mService.setLockCredential(nonePassword(), newPattern("123654"), PRIMARY_USER_ID);
+ clearCredential(PRIMARY_USER_ID, newPattern("123654"));
// Verify fingerprint is removed
verify(mFingerprintManager).removeAll(eq(PRIMARY_USER_ID), any());
@@ -335,14 +300,9 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
throws Exception {
final LockscreenCredential parentPassword = newPassword("parentPassword");
final LockscreenCredential profilePassword = newPassword("profilePassword");
- initializeStorageWithCredential(PRIMARY_USER_ID, parentPassword);
+ setCredential(PRIMARY_USER_ID, parentPassword);
mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
-
- assertTrue(mService.setLockCredential(
- profilePassword,
- nonePassword(),
- MANAGED_PROFILE_USER_ID));
-
+ setCredential(MANAGED_PROFILE_USER_ID, profilePassword);
verify(mRecoverableKeyStoreManager)
.lockScreenSecretChanged(CREDENTIAL_TYPE_PASSWORD, profilePassword.getCredential(),
MANAGED_PROFILE_USER_ID);
@@ -356,9 +316,8 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
final LockscreenCredential profilePassword = newPattern("12345");
mService.setSeparateProfileChallengeEnabled(
MANAGED_PROFILE_USER_ID, true, profilePassword);
- initializeStorageWithCredential(PRIMARY_USER_ID, parentPassword);
- // Create and verify separate profile credentials.
- testCreateCredential(MANAGED_PROFILE_USER_ID, profilePassword);
+ setCredential(PRIMARY_USER_ID, parentPassword);
+ setAndVerifyCredential(MANAGED_PROFILE_USER_ID, profilePassword);
mService.setSeparateProfileChallengeEnabled(
MANAGED_PROFILE_USER_ID, false, profilePassword);
@@ -372,7 +331,7 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
@Test
public void testVerifyCredential_forPrimaryUser_sendsCredentials() throws Exception {
final LockscreenCredential password = newPassword("password");
- initializeStorageWithCredential(PRIMARY_USER_ID, password);
+ setCredential(PRIMARY_USER_ID, password);
reset(mRecoverableKeyStoreManager);
mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */);
@@ -386,10 +345,7 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
public void testVerifyCredential_forProfileWithSeparateChallenge_sendsCredentials()
throws Exception {
final LockscreenCredential pattern = newPattern("12345");
- assertTrue(mService.setLockCredential(
- pattern,
- nonePassword(),
- MANAGED_PROFILE_USER_ID));
+ setCredential(MANAGED_PROFILE_USER_ID, pattern);
reset(mRecoverableKeyStoreManager);
mService.verifyCredential(pattern, MANAGED_PROFILE_USER_ID, 0 /* flags */);
@@ -403,7 +359,7 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
public void verifyCredential_forPrimaryUserWithUnifiedChallengeProfile_sendsCredentialsForBoth()
throws Exception {
final LockscreenCredential pattern = newPattern("12345");
- initializeStorageWithCredential(PRIMARY_USER_ID, pattern);
+ setCredential(PRIMARY_USER_ID, pattern);
mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
reset(mRecoverableKeyStoreManager);
@@ -423,7 +379,7 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
}
@Test
- public void testCredentialChangeNotPossibleInSecureFrpModeDuringSuw() {
+ public void testSetCredentialNotPossibleInSecureFrpModeDuringSuw() {
setUserSetupComplete(false);
setSecureFrpMode(true);
try {
@@ -433,21 +389,17 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
}
@Test
- public void testCredentialChangePossibleInSecureFrpModeAfterSuw() {
+ public void testSetCredentialPossibleInSecureFrpModeAfterSuw() throws RemoteException {
setUserSetupComplete(true);
setSecureFrpMode(true);
- assertTrue(mService.setLockCredential(newPassword("1234"), nonePassword(),
- PRIMARY_USER_ID));
+ setCredential(PRIMARY_USER_ID, newPassword("1234"));
}
@Test
public void testPasswordHistoryDisabledByDefault() throws Exception {
final int userId = PRIMARY_USER_ID;
checkPasswordHistoryLength(userId, 0);
- initializeStorageWithCredential(userId, nonePassword());
- checkPasswordHistoryLength(userId, 0);
-
- assertTrue(mService.setLockCredential(newPassword("1234"), nonePassword(), userId));
+ setCredential(userId, newPassword("1234"));
checkPasswordHistoryLength(userId, 0);
}
@@ -456,20 +408,18 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
final int userId = PRIMARY_USER_ID;
when(mDevicePolicyManager.getPasswordHistoryLength(any(), eq(userId))).thenReturn(3);
checkPasswordHistoryLength(userId, 0);
- initializeStorageWithCredential(userId, nonePassword());
- checkPasswordHistoryLength(userId, 0);
- assertTrue(mService.setLockCredential(newPassword("pass1"), nonePassword(), userId));
+ setCredential(userId, newPassword("pass1"));
checkPasswordHistoryLength(userId, 1);
- assertTrue(mService.setLockCredential(newPassword("pass2"), newPassword("pass1"), userId));
+ setCredential(userId, newPassword("pass2"), newPassword("pass1"));
checkPasswordHistoryLength(userId, 2);
- assertTrue(mService.setLockCredential(newPassword("pass3"), newPassword("pass2"), userId));
+ setCredential(userId, newPassword("pass3"), newPassword("pass2"));
checkPasswordHistoryLength(userId, 3);
// maximum length should have been reached
- assertTrue(mService.setLockCredential(newPassword("pass4"), newPassword("pass3"), userId));
+ setCredential(userId, newPassword("pass4"), newPassword("pass3"));
checkPasswordHistoryLength(userId, 3);
}
@@ -479,18 +429,11 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
assertEquals(expectedLen, hashes.length);
}
- private void testCreateCredential(int userId, LockscreenCredential credential)
- throws RemoteException {
- assertTrue(mService.setLockCredential(credential, nonePassword(), userId));
- assertVerifyCredentials(userId, credential);
- }
-
- private void testCreateCredentialFailsWithoutLockScreen(
+ private void testSetCredentialFailsWithoutLockScreen(
int userId, LockscreenCredential credential) throws RemoteException {
mService.mHasSecureLockScreen = false;
-
try {
- mService.setLockCredential(credential, null, userId);
+ mService.setLockCredential(credential, nonePassword(), userId);
fail("An exception should have been thrown.");
} catch (UnsupportedOperationException e) {
// Success - the exception was expected.
@@ -499,14 +442,14 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
assertEquals(CREDENTIAL_TYPE_NONE, mService.getCredentialType(userId));
}
- private void testChangeCredentials(int userId, LockscreenCredential newCredential,
+ private void testChangeCredential(int userId, LockscreenCredential newCredential,
LockscreenCredential oldCredential) throws RemoteException {
- initializeStorageWithCredential(userId, oldCredential);
- assertTrue(mService.setLockCredential(newCredential, oldCredential, userId));
- assertVerifyCredentials(userId, newCredential);
+ setCredential(userId, oldCredential);
+ setCredential(userId, newCredential, oldCredential);
+ assertVerifyCredential(userId, newCredential);
}
- private void assertVerifyCredentials(int userId, LockscreenCredential credential)
+ private void assertVerifyCredential(int userId, LockscreenCredential credential)
throws RemoteException{
VerifyCredentialResponse response = mService.verifyCredential(credential, userId,
0 /* flags */);
@@ -533,16 +476,29 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
badCredential, userId, 0 /* flags */).getResponseCode());
}
- private void initializeStorageWithCredential(int userId, LockscreenCredential credential)
+ private void setAndVerifyCredential(int userId, LockscreenCredential newCredential)
throws RemoteException {
- assertEquals(0, mGateKeeperService.getSecureUserId(userId));
- synchronized (mService.mSpManager) {
- mService.initializeSyntheticPasswordLocked(userId);
- }
- if (credential.isNone()) {
+ setCredential(userId, newCredential);
+ assertVerifyCredential(userId, newCredential);
+ }
+
+ private void setCredential(int userId, LockscreenCredential newCredential)
+ throws RemoteException {
+ setCredential(userId, newCredential, nonePassword());
+ }
+
+ private void clearCredential(int userId, LockscreenCredential oldCredential)
+ throws RemoteException {
+ setCredential(userId, nonePassword(), oldCredential);
+ }
+
+ private void setCredential(int userId, LockscreenCredential newCredential,
+ LockscreenCredential oldCredential) throws RemoteException {
+ assertTrue(mService.setLockCredential(newCredential, oldCredential, userId));
+ assertEquals(newCredential.getType(), mService.getCredentialType(userId));
+ if (newCredential.isNone()) {
assertEquals(0, mGateKeeperService.getSecureUserId(userId));
} else {
- assertTrue(mService.setLockCredential(credential, nonePassword(), userId));
assertNotEquals(0, mGateKeeperService.getSecureUserId(userId));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
index fc0ca7eda243..10869dab7f22 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
@@ -42,14 +42,13 @@ import org.junit.runner.RunWith;
public class LockscreenFrpTest extends BaseLockSettingsServiceTests {
@Before
- public void setDeviceNotProvisioned() throws Exception {
+ public void setUp() throws Exception {
+ PropertyInvalidatedCache.disableForTestMode();
+
// FRP credential can only be verified prior to provisioning
setDeviceProvisioned(false);
- }
- @Before
- public void disableProcessCaches() {
- PropertyInvalidatedCache.disableForTestMode();
+ mService.initializeSyntheticPassword(PRIMARY_USER_ID);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index b00467cf8637..57593cf660f2 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -19,7 +19,6 @@ package com.android.server.locksettings;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN;
-import static com.android.internal.widget.LockPatternUtils.CURRENT_LSKF_BASED_PROTECTOR_ID_KEY;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -59,9 +58,6 @@ import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-
/**
* atest FrameworksServicesTests:SyntheticPasswordTests
@@ -127,21 +123,11 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
return mGateKeeperService.getSecureUserId(SyntheticPasswordManager.fakeUserId(userId)) != 0;
}
- private boolean hasSyntheticPassword(int userId) throws RemoteException {
- return mService.getLong(CURRENT_LSKF_BASED_PROTECTOR_ID_KEY, 0, userId) != 0;
- }
-
- private void initializeCredential(LockscreenCredential password, int userId)
+ private void initSpAndSetCredential(int userId, LockscreenCredential credential)
throws RemoteException {
- assertTrue(mService.setLockCredential(password, nonePassword(), userId));
- assertEquals(CREDENTIAL_TYPE_PASSWORD, mService.getCredentialType(userId));
- assertTrue(mService.isSyntheticPasswordBasedCredential(userId));
- }
-
- protected void initializeSyntheticPassword(int userId) {
- synchronized (mService.mSpManager) {
- mService.initializeSyntheticPasswordLocked(userId);
- }
+ mService.initializeSyntheticPassword(userId);
+ assertTrue(mService.setLockCredential(credential, nonePassword(), userId));
+ assertEquals(credential.getType(), mService.getCredentialType(userId));
}
// Tests that the FRP credential is updated when an LSKF-based protector is created for the user
@@ -149,7 +135,7 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
@Test
public void testFrpCredentialSyncedIfDeviceProvisioned() throws RemoteException {
setDeviceProvisioned(true);
- initializeSyntheticPassword(PRIMARY_USER_ID);
+ mService.initializeSyntheticPassword(PRIMARY_USER_ID);
verify(mStorage.mPersistentDataBlockManager).setFrpCredentialHandle(any());
}
@@ -159,7 +145,7 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
@Test
public void testEmptyFrpCredentialNotSyncedIfDeviceNotProvisioned() throws RemoteException {
setDeviceProvisioned(false);
- initializeSyntheticPassword(PRIMARY_USER_ID);
+ mService.initializeSyntheticPassword(PRIMARY_USER_ID);
verify(mStorage.mPersistentDataBlockManager, never()).setFrpCredentialHandle(any());
}
@@ -169,7 +155,7 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
@Test
public void testNonEmptyFrpCredentialSyncedIfDeviceNotProvisioned() throws RemoteException {
setDeviceProvisioned(false);
- initializeSyntheticPassword(PRIMARY_USER_ID);
+ mService.initializeSyntheticPassword(PRIMARY_USER_ID);
verify(mStorage.mPersistentDataBlockManager, never()).setFrpCredentialHandle(any());
mService.setLockCredential(newPassword("password"), nonePassword(), PRIMARY_USER_ID);
verify(mStorage.mPersistentDataBlockManager).setFrpCredentialHandle(any());
@@ -180,7 +166,7 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
final LockscreenCredential password = newPassword("password");
final LockscreenCredential newPassword = newPassword("newpassword");
- initializeCredential(password, PRIMARY_USER_ID);
+ initSpAndSetCredential(PRIMARY_USER_ID, password);
long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
mService.setLockCredential(newPassword, password, PRIMARY_USER_ID);
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
@@ -193,7 +179,7 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
LockscreenCredential password = newPassword("password");
LockscreenCredential badPassword = newPassword("badpassword");
- initializeCredential(password, PRIMARY_USER_ID);
+ initSpAndSetCredential(PRIMARY_USER_ID, password);
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
verify(mActivityManager).unlockUser2(eq(PRIMARY_USER_ID), any());
@@ -207,7 +193,7 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
LockscreenCredential password = newPassword("password");
LockscreenCredential badPassword = newPassword("newpassword");
- initializeCredential(password, PRIMARY_USER_ID);
+ initSpAndSetCredential(PRIMARY_USER_ID, password);
long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
// clear password
mService.setLockCredential(nonePassword(), password, PRIMARY_USER_ID);
@@ -225,7 +211,7 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
LockscreenCredential password = newPassword("password");
LockscreenCredential badPassword = newPassword("new");
- initializeCredential(password, PRIMARY_USER_ID);
+ initSpAndSetCredential(PRIMARY_USER_ID, password);
mService.setLockCredential(badPassword, password, PRIMARY_USER_ID);
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
badPassword, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
@@ -242,7 +228,7 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
public void testVerifyPassesPrimaryUserAuthSecret() throws RemoteException {
LockscreenCredential password = newPassword("password");
- initializeCredential(password, PRIMARY_USER_ID);
+ initSpAndSetCredential(PRIMARY_USER_ID, password);
reset(mAuthSecretService);
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
@@ -253,7 +239,7 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
public void testSecondaryUserDoesNotPassAuthSecret() throws RemoteException {
LockscreenCredential password = newPassword("password");
- initializeCredential(password, SECONDARY_USER_ID);
+ initSpAndSetCredential(SECONDARY_USER_ID, password);
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
password, SECONDARY_USER_ID, 0 /* flags */).getResponseCode());
verify(mAuthSecretService, never()).setPrimaryUserCredential(any(byte[].class));
@@ -262,7 +248,7 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
@Test
public void testUnlockUserKeyIfUnsecuredPassesPrimaryUserAuthSecret() throws RemoteException {
LockscreenCredential password = newPassword("password");
- initializeCredential(password, PRIMARY_USER_ID);
+ initSpAndSetCredential(PRIMARY_USER_ID, password);
mService.setLockCredential(nonePassword(), password, PRIMARY_USER_ID);
reset(mAuthSecretService);
@@ -275,7 +261,7 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
LockscreenCredential password = newPassword("password");
LockscreenCredential pattern = newPattern("123654");
byte[] token = "some-high-entropy-secure-token".getBytes();
- initializeCredential(password, PRIMARY_USER_ID);
+ initSpAndSetCredential(PRIMARY_USER_ID, password);
// Disregard any reportPasswordChanged() invocations as part of credential setup.
flushHandlerTasks();
reset(mDevicePolicyManager);
@@ -310,7 +296,7 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
LockscreenCredential password = newPassword("password");
LockscreenCredential pattern = newPattern("123654");
byte[] token = "some-high-entropy-secure-token".getBytes();
- initializeCredential(password, PRIMARY_USER_ID);
+ initSpAndSetCredential(PRIMARY_USER_ID, password);
byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
@@ -336,7 +322,7 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
LockscreenCredential pattern = newPattern("123654");
LockscreenCredential newPassword = newPassword("password");
byte[] token = "some-high-entropy-secure-token".getBytes();
- initializeCredential(password, PRIMARY_USER_ID);
+ initSpAndSetCredential(PRIMARY_USER_ID, password);
byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
@@ -356,38 +342,20 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
}
@Test
- public void testEscrowTokenActivatedImmediatelyIfNoUserPasswordNeedsMigration()
- throws RemoteException {
- final byte[] token = "some-high-entropy-secure-token".getBytes();
- long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
- assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
- assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
- assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
- }
-
- @Test
- public void testEscrowTokenActivatedImmediatelyIfNoUserPasswordNoMigration()
- throws RemoteException {
+ public void testEscrowTokenActivatedImmediatelyIfNoUserPassword() throws RemoteException {
final byte[] token = "some-high-entropy-secure-token".getBytes();
- // By first setting a password and then clearing it, we enter the state where SP is
- // initialized but the user currently has no password
- initializeCredential(newPassword("password"), PRIMARY_USER_ID);
- assertTrue(mService.setLockCredential(nonePassword(), newPassword("password"),
- PRIMARY_USER_ID));
- assertTrue(mService.isSyntheticPasswordBasedCredential(PRIMARY_USER_ID));
-
+ mService.initializeSyntheticPassword(PRIMARY_USER_ID);
long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
- assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
}
@Test
public void testEscrowTokenActivatedLaterWithUserPassword() throws RemoteException {
byte[] token = "some-high-entropy-secure-token".getBytes();
LockscreenCredential password = newPassword("password");
- mService.setLockCredential(password, nonePassword(), PRIMARY_USER_ID);
+ initSpAndSetCredential(PRIMARY_USER_ID, password);
long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
// Token not activated immediately since user password exists
assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
@@ -407,6 +375,7 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
when(mUserManagerInternal.isUserManaged(PRIMARY_USER_ID)).thenReturn(false);
when(mDeviceStateCache.isDeviceProvisioned()).thenReturn(true);
+ mService.initializeSyntheticPassword(PRIMARY_USER_ID);
try {
mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
fail("Escrow token should not be possible on unmanaged device");
@@ -421,7 +390,7 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
LockscreenCredential password = newPassword("password");
LockscreenCredential pattern = newPattern("123654");
- initializeCredential(password, PRIMARY_USER_ID);
+ initSpAndSetCredential(PRIMARY_USER_ID, password);
long handle0 = mLocalService.addEscrowToken(token0, PRIMARY_USER_ID, null);
long handle1 = mLocalService.addEscrowToken(token1, PRIMARY_USER_ID, null);
@@ -450,6 +419,7 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
byte[] token = "some-high-entropy-secure-token".getBytes();
mService.mHasSecureLockScreen = false;
+ mService.initializeSyntheticPassword(PRIMARY_USER_ID);
long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
@@ -473,7 +443,7 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
@Test
public void testGetHashFactorPrimaryUser() throws RemoteException {
LockscreenCredential password = newPassword("password");
- mService.setLockCredential(password, nonePassword(), PRIMARY_USER_ID);
+ initSpAndSetCredential(PRIMARY_USER_ID, password);
byte[] hashFactor = mService.getHashFactor(password, PRIMARY_USER_ID);
assertNotNull(hashFactor);
@@ -486,6 +456,9 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
@Test
public void testGetHashFactorManagedProfileUnifiedChallenge() throws RemoteException {
+ mService.initializeSyntheticPassword(PRIMARY_USER_ID);
+ mService.initializeSyntheticPassword(MANAGED_PROFILE_USER_ID);
+
LockscreenCredential pattern = newPattern("1236");
mService.setLockCredential(pattern, nonePassword(), PRIMARY_USER_ID);
mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
@@ -494,6 +467,9 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
@Test
public void testGetHashFactorManagedProfileSeparateChallenge() throws RemoteException {
+ mService.initializeSyntheticPassword(PRIMARY_USER_ID);
+ mService.initializeSyntheticPassword(MANAGED_PROFILE_USER_ID);
+
LockscreenCredential primaryPassword = newPassword("primary");
LockscreenCredential profilePassword = newPassword("profile");
mService.setLockCredential(primaryPassword, nonePassword(), PRIMARY_USER_ID);
@@ -594,7 +570,7 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
LockscreenCredential password = newPassword("testGsiDisablesAuthSecret-password");
- initializeCredential(password, PRIMARY_USER_ID);
+ initSpAndSetCredential(PRIMARY_USER_ID, password);
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
verify(mAuthSecretService, never()).setPrimaryUserCredential(any(byte[].class));
@@ -604,7 +580,7 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
public void testUnlockUserWithToken() throws Exception {
LockscreenCredential password = newPassword("password");
byte[] token = "some-high-entropy-secure-token".getBytes();
- initializeCredential(password, PRIMARY_USER_ID);
+ initSpAndSetCredential(PRIMARY_USER_ID, password);
// Disregard any reportPasswordChanged() invocations as part of credential setup.
flushHandlerTasks();
reset(mDevicePolicyManager);
@@ -625,7 +601,7 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
@Test
public void testPasswordChange_NoOrphanedFilesLeft() throws Exception {
LockscreenCredential password = newPassword("password");
- initializeCredential(password, PRIMARY_USER_ID);
+ initSpAndSetCredential(PRIMARY_USER_ID, password);
assertTrue(mService.setLockCredential(password, password, PRIMARY_USER_ID));
assertNoOrphanedFilesLeft(PRIMARY_USER_ID);
}
@@ -633,6 +609,7 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
@Test
public void testAddingEscrowToken_NoOrphanedFilesLeft() throws Exception {
final byte[] token = "some-high-entropy-secure-token".getBytes();
+ mService.initializeSyntheticPassword(PRIMARY_USER_ID);
for (int i = 0; i < 16; i++) {
long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/WeakEscrowTokenTests.java b/services/tests/servicestests/src/com/android/server/locksettings/WeakEscrowTokenTests.java
index 51ddcef425ce..2c9ba34e850f 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/WeakEscrowTokenTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/WeakEscrowTokenTests.java
@@ -40,6 +40,7 @@ import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.VerifyCredentialResponse;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -49,6 +50,11 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class WeakEscrowTokenTests extends BaseLockSettingsServiceTests{
+ @Before
+ public void setUp() {
+ mService.initializeSyntheticPassword(PRIMARY_USER_ID);
+ }
+
@Test
public void testWeakTokenActivatedImmediatelyIfNoUserPassword()
throws RemoteException {
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
index 6c13a6fe04a0..966c0479d69d 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
@@ -36,7 +36,7 @@ public class WeaverBasedSyntheticPasswordTests extends SyntheticPasswordTests {
assertEquals(Sets.newHashSet(), mPasswordSlotManager.getUsedSlots());
mStorage.writePersistentDataBlock(PersistentData.TYPE_SP_WEAVER, frpWeaverSlot, 0,
new byte[1]);
- initializeSyntheticPassword(userId); // This should allocate a Weaver slot.
+ mService.initializeSyntheticPassword(userId); // This should allocate a Weaver slot.
assertEquals(Sets.newHashSet(1), mPasswordSlotManager.getUsedSlots());
}
@@ -52,7 +52,7 @@ public class WeaverBasedSyntheticPasswordTests extends SyntheticPasswordTests {
assertEquals(Sets.newHashSet(), mPasswordSlotManager.getUsedSlots());
mStorage.writePersistentDataBlock(PersistentData.TYPE_SP_WEAVER, frpWeaverSlot, 0,
new byte[1]);
- initializeSyntheticPassword(userId); // This should allocate a Weaver slot.
+ mService.initializeSyntheticPassword(userId); // This should allocate a Weaver slot.
assertEquals(Sets.newHashSet(0), mPasswordSlotManager.getUsedSlots());
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
new file mode 100644
index 000000000000..54fa272dd3b6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
@@ -0,0 +1,840 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.usage.UsageEvents;
+import android.app.usage.UsageEvents.Event;
+import android.app.usage.UsageStatsManagerInternal;
+import android.app.usage.UsageStatsManagerInternal.UsageEventListener;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.InstallSourceInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.ParceledListSlice;
+import android.os.FileUtils;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
+import android.util.AtomicFile;
+import android.util.SparseSetArray;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.pm.permission.PermissionManagerServiceInternal;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.internal.util.reflection.FieldSetter;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for {@link com.android.server.pm.BackgroundInstallControlService}
+ */
+@Presubmit
+public final class BackgroundInstallControlServiceTest {
+ private static final String INSTALLER_NAME_1 = "installer1";
+ private static final String INSTALLER_NAME_2 = "installer2";
+ private static final String PACKAGE_NAME_1 = "package1";
+ private static final String PACKAGE_NAME_2 = "package2";
+ private static final String PACKAGE_NAME_3 = "package3";
+ private static final int USER_ID_1 = 1;
+ private static final int USER_ID_2 = 2;
+ private static final long USAGE_EVENT_TIMESTAMP_1 = 1000;
+ private static final long USAGE_EVENT_TIMESTAMP_2 = 2000;
+ private static final long USAGE_EVENT_TIMESTAMP_3 = 3000;
+ private static final long PACKAGE_ADD_TIMESTAMP_1 = 1500;
+
+ private BackgroundInstallControlService mBackgroundInstallControlService;
+ private PackageManagerInternal.PackageListObserver mPackageListObserver;
+ private UsageEventListener mUsageEventListener;
+ private TestLooper mTestLooper;
+ private Looper mLooper;
+ private File mFile;
+
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private IPackageManager mIPackageManager;
+ @Mock
+ private PackageManagerInternal mPackageManagerInternal;
+ @Mock
+ private UsageStatsManagerInternal mUsageStatsManagerInternal;
+ @Mock
+ private PermissionManagerServiceInternal mPermissionManager;
+ @Captor
+ private ArgumentCaptor<PackageManagerInternal.PackageListObserver> mPackageListObserverCaptor;
+ @Captor
+ private ArgumentCaptor<UsageEventListener> mUsageEventListenerCaptor;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mTestLooper = new TestLooper();
+ mLooper = mTestLooper.getLooper();
+ mFile = new File(
+ InstrumentationRegistry.getInstrumentation().getContext().getCacheDir(),
+ "test");
+ mBackgroundInstallControlService = new BackgroundInstallControlService(
+ new MockInjector(mContext));
+
+ verify(mUsageStatsManagerInternal).registerListener(mUsageEventListenerCaptor.capture());
+ mUsageEventListener = mUsageEventListenerCaptor.getValue();
+
+ mBackgroundInstallControlService.onStart(true);
+ verify(mPackageManagerInternal).getPackageList(mPackageListObserverCaptor.capture());
+ mPackageListObserver = mPackageListObserverCaptor.getValue();
+ }
+
+ @After
+ public void tearDown() {
+ FileUtils.deleteContentsAndDir(mFile);
+ }
+
+ @Test
+ public void testInitBackgroundInstalledPackages_empty() {
+ assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+ mBackgroundInstallControlService.initBackgroundInstalledPackages();
+ assertNotNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+ assertEquals(0,
+ mBackgroundInstallControlService.getBackgroundInstalledPackages().size());
+ }
+
+ @Test
+ public void testInitBackgroundInstalledPackages_one() {
+ AtomicFile atomicFile = new AtomicFile(mFile);
+ FileOutputStream fileOutputStream;
+ try {
+ fileOutputStream = atomicFile.startWrite();
+ } catch (IOException e) {
+ fail("Failed to start write to states protobuf." + e);
+ return;
+ }
+
+ // Write test data to the file on the disk.
+ try {
+ ProtoOutputStream protoOutputStream = new ProtoOutputStream(fileOutputStream);
+ long token = protoOutputStream.start(
+ BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+ protoOutputStream.write(
+ BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_1);
+ protoOutputStream.write(
+ BackgroundInstalledPackageProto.USER_ID, USER_ID_1 + 1);
+ protoOutputStream.end(token);
+ protoOutputStream.flush();
+ atomicFile.finishWrite(fileOutputStream);
+ } catch (Exception e) {
+ fail("Failed to finish write to states protobuf. " + e);
+ atomicFile.failWrite(fileOutputStream);
+ }
+
+ assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+ mBackgroundInstallControlService.initBackgroundInstalledPackages();
+ var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
+ assertNotNull(packages);
+ assertEquals(1, packages.size());
+ assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
+ }
+
+ @Test
+ public void testInitBackgroundInstalledPackages_two() {
+ AtomicFile atomicFile = new AtomicFile(mFile);
+ FileOutputStream fileOutputStream;
+ try {
+ fileOutputStream = atomicFile.startWrite();
+ } catch (IOException e) {
+ fail("Failed to start write to states protobuf." + e);
+ return;
+ }
+
+ // Write test data to the file on the disk.
+ try {
+ ProtoOutputStream protoOutputStream = new ProtoOutputStream(fileOutputStream);
+
+ long token = protoOutputStream.start(
+ BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+ protoOutputStream.write(
+ BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_1);
+ protoOutputStream.write(
+ BackgroundInstalledPackageProto.USER_ID, USER_ID_1 + 1);
+ protoOutputStream.end(token);
+
+ token = protoOutputStream.start(
+ BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+ protoOutputStream.write(
+ BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_2);
+ protoOutputStream.write(
+ BackgroundInstalledPackageProto.USER_ID, USER_ID_2 + 1);
+ protoOutputStream.end(token);
+
+ protoOutputStream.flush();
+ atomicFile.finishWrite(fileOutputStream);
+ } catch (Exception e) {
+ fail("Failed to finish write to states protobuf. " + e);
+ atomicFile.failWrite(fileOutputStream);
+ }
+
+ assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+ mBackgroundInstallControlService.initBackgroundInstalledPackages();
+ var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
+ assertNotNull(packages);
+ assertEquals(2, packages.size());
+ assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
+ assertTrue(packages.contains(USER_ID_2, PACKAGE_NAME_2));
+ }
+
+ @Test
+ public void testWriteBackgroundInstalledPackagesToDisk_empty() {
+ assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+ mBackgroundInstallControlService.initBackgroundInstalledPackages();
+ var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
+ assertNotNull(packages);
+ mBackgroundInstallControlService.writeBackgroundInstalledPackagesToDisk();
+
+ // Read the file on the disk to verify
+ var packagesInDisk = new SparseSetArray<>();
+ AtomicFile atomicFile = new AtomicFile(mFile);
+ try (FileInputStream fileInputStream = atomicFile.openRead()) {
+ ProtoInputStream protoInputStream = new ProtoInputStream(fileInputStream);
+
+ while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ if (protoInputStream.getFieldNumber()
+ != (int) BackgroundInstalledPackagesProto.BG_INSTALLED_PKG) {
+ continue;
+ }
+ long token = protoInputStream.start(
+ BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+ String packageName = null;
+ int userId = UserHandle.USER_NULL;
+ while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (protoInputStream.getFieldNumber()) {
+ case (int) BackgroundInstalledPackageProto.PACKAGE_NAME:
+ packageName = protoInputStream.readString(
+ BackgroundInstalledPackageProto.PACKAGE_NAME);
+ break;
+ case (int) BackgroundInstalledPackageProto.USER_ID:
+ userId = protoInputStream.readInt(
+ BackgroundInstalledPackageProto.USER_ID) - 1;
+ break;
+ default:
+ fail("Undefined field in proto: "
+ + protoInputStream.getFieldNumber());
+ }
+ }
+ protoInputStream.end(token);
+ if (packageName != null && userId != UserHandle.USER_NULL) {
+ packagesInDisk.add(userId, packageName);
+ } else {
+ fail("Fails to get packageName or UserId from proto file");
+ }
+ }
+ } catch (IOException e) {
+ fail("Error reading state from the disk. " + e);
+ }
+
+ assertEquals(0, packagesInDisk.size());
+ assertEquals(packages.size(), packagesInDisk.size());
+ }
+
+ @Test
+ public void testWriteBackgroundInstalledPackagesToDisk_one() {
+ assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+ mBackgroundInstallControlService.initBackgroundInstalledPackages();
+ var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
+ assertNotNull(packages);
+
+ packages.add(USER_ID_1, PACKAGE_NAME_1);
+ mBackgroundInstallControlService.writeBackgroundInstalledPackagesToDisk();
+
+ // Read the file on the disk to verify
+ var packagesInDisk = new SparseSetArray<>();
+ AtomicFile atomicFile = new AtomicFile(mFile);
+ try (FileInputStream fileInputStream = atomicFile.openRead()) {
+ ProtoInputStream protoInputStream = new ProtoInputStream(fileInputStream);
+
+ while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ if (protoInputStream.getFieldNumber()
+ != (int) BackgroundInstalledPackagesProto.BG_INSTALLED_PKG) {
+ continue;
+ }
+ long token = protoInputStream.start(
+ BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+ String packageName = null;
+ int userId = UserHandle.USER_NULL;
+ while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (protoInputStream.getFieldNumber()) {
+ case (int) BackgroundInstalledPackageProto.PACKAGE_NAME:
+ packageName = protoInputStream.readString(
+ BackgroundInstalledPackageProto.PACKAGE_NAME);
+ break;
+ case (int) BackgroundInstalledPackageProto.USER_ID:
+ userId = protoInputStream.readInt(
+ BackgroundInstalledPackageProto.USER_ID) - 1;
+ break;
+ default:
+ fail("Undefined field in proto: "
+ + protoInputStream.getFieldNumber());
+ }
+ }
+ protoInputStream.end(token);
+ if (packageName != null && userId != UserHandle.USER_NULL) {
+ packagesInDisk.add(userId, packageName);
+ } else {
+ fail("Fails to get packageName or UserId from proto file");
+ }
+ }
+ } catch (IOException e) {
+ fail("Error reading state from the disk. " + e);
+ }
+
+ assertEquals(1, packagesInDisk.size());
+ assertEquals(packages.size(), packagesInDisk.size());
+ assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
+ }
+
+ @Test
+ public void testWriteBackgroundInstalledPackagesToDisk_two() {
+ assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+ mBackgroundInstallControlService.initBackgroundInstalledPackages();
+ var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
+ assertNotNull(packages);
+
+ packages.add(USER_ID_1, PACKAGE_NAME_1);
+ packages.add(USER_ID_2, PACKAGE_NAME_2);
+ mBackgroundInstallControlService.writeBackgroundInstalledPackagesToDisk();
+
+ // Read the file on the disk to verify
+ var packagesInDisk = new SparseSetArray<>();
+ AtomicFile atomicFile = new AtomicFile(mFile);
+ try (FileInputStream fileInputStream = atomicFile.openRead()) {
+ ProtoInputStream protoInputStream = new ProtoInputStream(fileInputStream);
+
+ while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ if (protoInputStream.getFieldNumber()
+ != (int) BackgroundInstalledPackagesProto.BG_INSTALLED_PKG) {
+ continue;
+ }
+ long token = protoInputStream.start(
+ BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
+ String packageName = null;
+ int userId = UserHandle.USER_NULL;
+ while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (protoInputStream.getFieldNumber()) {
+ case (int) BackgroundInstalledPackageProto.PACKAGE_NAME:
+ packageName = protoInputStream.readString(
+ BackgroundInstalledPackageProto.PACKAGE_NAME);
+ break;
+ case (int) BackgroundInstalledPackageProto.USER_ID:
+ userId = protoInputStream.readInt(
+ BackgroundInstalledPackageProto.USER_ID) - 1;
+ break;
+ default:
+ fail("Undefined field in proto: "
+ + protoInputStream.getFieldNumber());
+ }
+ }
+ protoInputStream.end(token);
+ if (packageName != null && userId != UserHandle.USER_NULL) {
+ packagesInDisk.add(userId, packageName);
+ } else {
+ fail("Fails to get packageName or UserId from proto file");
+ }
+ }
+ } catch (IOException e) {
+ fail("Error reading state from the disk. " + e);
+ }
+
+ assertEquals(2, packagesInDisk.size());
+ assertEquals(packages.size(), packagesInDisk.size());
+ assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
+ assertTrue(packages.contains(USER_ID_2, PACKAGE_NAME_2));
+ }
+
+ @Test
+ public void testHandleUsageEvent_permissionDenied() {
+ assertEquals(0,
+ mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+ doReturn(PackageManager.PERMISSION_DENIED).when(mPermissionManager).checkPermission(
+ anyString(), anyString(), anyInt());
+ generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
+ USER_ID_1, INSTALLER_NAME_1, 0);
+ mTestLooper.dispatchAll();
+ assertEquals(0,
+ mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+ }
+
+ @Test
+ public void testHandleUsageEvent_permissionGranted() {
+ assertEquals(0,
+ mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+ doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
+ anyString(), anyString(), anyInt());
+ generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
+ USER_ID_1, INSTALLER_NAME_1, 0);
+ mTestLooper.dispatchAll();
+ assertEquals(1,
+ mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+ }
+
+ @Test
+ public void testHandleUsageEvent_ignoredEvent() {
+ assertEquals(0,
+ mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+ doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
+ anyString(), anyString(), anyInt());
+ generateUsageEvent(UsageEvents.Event.USER_INTERACTION,
+ USER_ID_1, INSTALLER_NAME_1, 0);
+ mTestLooper.dispatchAll();
+ assertEquals(0,
+ mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+ }
+
+ @Test
+ public void testHandleUsageEvent_firstActivityResumedHalfTimeFrame() {
+ assertEquals(0,
+ mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+ doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
+ anyString(), anyString(), anyInt());
+ generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
+ USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
+ mTestLooper.dispatchAll();
+
+ var installerForegroundTimeFrames =
+ mBackgroundInstallControlService.getInstallerForegroundTimeFrames();
+ assertEquals(1, installerForegroundTimeFrames.numMaps());
+ assertEquals(1, installerForegroundTimeFrames.numElementsForKey(USER_ID_1));
+
+ var foregroundTimeFrames = installerForegroundTimeFrames.get(USER_ID_1, INSTALLER_NAME_1);
+ assertEquals(1, foregroundTimeFrames.size());
+
+ var foregroundTimeFrame = foregroundTimeFrames.first();
+ assertEquals(USAGE_EVENT_TIMESTAMP_1, foregroundTimeFrame.startTimeStampMillis);
+ assertFalse(foregroundTimeFrame.isDone());
+ }
+
+ @Test
+ public void testHandleUsageEvent_firstActivityResumedOneTimeFrame() {
+ assertEquals(0,
+ mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+ doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
+ anyString(), anyString(), anyInt());
+ generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
+ USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
+ generateUsageEvent(Event.ACTIVITY_STOPPED,
+ USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
+ mTestLooper.dispatchAll();
+
+ var installerForegroundTimeFrames =
+ mBackgroundInstallControlService.getInstallerForegroundTimeFrames();
+ assertEquals(1, installerForegroundTimeFrames.numMaps());
+ assertEquals(1, installerForegroundTimeFrames.numElementsForKey(USER_ID_1));
+
+ var foregroundTimeFrames = installerForegroundTimeFrames.get(USER_ID_1, INSTALLER_NAME_1);
+ assertEquals(1, foregroundTimeFrames.size());
+
+ var foregroundTimeFrame = foregroundTimeFrames.first();
+ assertEquals(USAGE_EVENT_TIMESTAMP_1, foregroundTimeFrame.startTimeStampMillis);
+ assertTrue(foregroundTimeFrame.isDone());
+ }
+
+ @Test
+ public void testHandleUsageEvent_firstActivityResumedOneAndHalfTimeFrame() {
+ assertEquals(0,
+ mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+ doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
+ anyString(), anyString(), anyInt());
+ generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
+ USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
+ generateUsageEvent(Event.ACTIVITY_STOPPED,
+ USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
+ generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
+ USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3);
+ mTestLooper.dispatchAll();
+
+ var installerForegroundTimeFrames =
+ mBackgroundInstallControlService.getInstallerForegroundTimeFrames();
+ assertEquals(1, installerForegroundTimeFrames.numMaps());
+ assertEquals(1, installerForegroundTimeFrames.numElementsForKey(USER_ID_1));
+
+ var foregroundTimeFrames = installerForegroundTimeFrames.get(USER_ID_1, INSTALLER_NAME_1);
+ assertEquals(2, foregroundTimeFrames.size());
+
+ var foregroundTimeFrame1 = foregroundTimeFrames.first();
+ assertEquals(USAGE_EVENT_TIMESTAMP_1, foregroundTimeFrame1.startTimeStampMillis);
+ assertTrue(foregroundTimeFrame1.isDone());
+
+ var foregroundTimeFrame2 = foregroundTimeFrames.last();
+ assertEquals(USAGE_EVENT_TIMESTAMP_3, foregroundTimeFrame2.startTimeStampMillis);
+ assertFalse(foregroundTimeFrame2.isDone());
+ }
+
+ @Test
+ public void testHandleUsageEvent_firstNoneActivityResumed() {
+ assertEquals(0,
+ mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
+ doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
+ anyString(), anyString(), anyInt());
+ generateUsageEvent(Event.ACTIVITY_STOPPED,
+ USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
+ mTestLooper.dispatchAll();
+
+ var installerForegroundTimeFrames =
+ mBackgroundInstallControlService.getInstallerForegroundTimeFrames();
+ assertEquals(1, installerForegroundTimeFrames.numMaps());
+ assertEquals(1, installerForegroundTimeFrames.numElementsForKey(USER_ID_1));
+
+ var foregroundTimeFrames = installerForegroundTimeFrames.get(USER_ID_1, INSTALLER_NAME_1);
+ assertEquals(0, foregroundTimeFrames.size());
+ }
+
+ @Test
+ public void testHandleUsageEvent_packageAddedNoUsageEvent() throws
+ RemoteException, NoSuchFieldException {
+ assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+ InstallSourceInfo installSourceInfo = new InstallSourceInfo(
+ /* initiatingPackageName = */ null, /* initiatingPackageSigningInfo = */ null,
+ /* originatingPackageName = */ null,
+ /* installingPackageName = */ INSTALLER_NAME_1);
+ assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1);
+ when(mIPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
+ ApplicationInfo appInfo = mock(ApplicationInfo.class);
+
+ when(mIPackageManager.getApplicationInfo(
+ eq(PACKAGE_NAME_1),
+ eq(0L),
+ anyInt())
+ ).thenReturn(appInfo);
+
+ long createTimestamp = PACKAGE_ADD_TIMESTAMP_1
+ - (System.currentTimeMillis() - SystemClock.uptimeMillis());
+ FieldSetter.setField(appInfo,
+ ApplicationInfo.class.getDeclaredField("createTimestamp"),
+ createTimestamp);
+
+ int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
+ assertEquals(USER_ID_1, UserHandle.getUserId(uid));
+
+ mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid);
+ mTestLooper.dispatchAll();
+
+ var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
+ assertNotNull(packages);
+ assertEquals(1, packages.size());
+ assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
+ }
+
+ @Test
+ public void testHandleUsageEvent_packageAddedInsideTimeFrame() throws
+ RemoteException, NoSuchFieldException {
+ assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+ InstallSourceInfo installSourceInfo = new InstallSourceInfo(
+ /* initiatingPackageName = */ null, /* initiatingPackageSigningInfo = */ null,
+ /* originatingPackageName = */ null,
+ /* installingPackageName = */ INSTALLER_NAME_1);
+ assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1);
+ when(mIPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
+ ApplicationInfo appInfo = mock(ApplicationInfo.class);
+
+ when(mIPackageManager.getApplicationInfo(
+ eq(PACKAGE_NAME_1),
+ eq(0L),
+ anyInt())
+ ).thenReturn(appInfo);
+
+ long createTimestamp = PACKAGE_ADD_TIMESTAMP_1
+ - (System.currentTimeMillis() - SystemClock.uptimeMillis());
+ FieldSetter.setField(appInfo,
+ ApplicationInfo.class.getDeclaredField("createTimestamp"),
+ createTimestamp);
+
+ int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
+ assertEquals(USER_ID_1, UserHandle.getUserId(uid));
+
+ // The following 2 usage events generation is the only difference from the
+ // testHandleUsageEvent_packageAddedNoUsageEvent test.
+ // The 2 usage events make the package adding inside a time frame.
+ // So it's not a background install. Thus, it's null for the return of
+ // mBackgroundInstallControlService.getBackgroundInstalledPackages()
+ doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
+ anyString(), anyString(), anyInt());
+ generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
+ USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
+ generateUsageEvent(Event.ACTIVITY_STOPPED,
+ USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
+
+ mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid);
+ mTestLooper.dispatchAll();
+ assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+ }
+
+ @Test
+ public void testHandleUsageEvent_packageAddedOutsideTimeFrame1() throws
+ RemoteException, NoSuchFieldException {
+ assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+ InstallSourceInfo installSourceInfo = new InstallSourceInfo(
+ /* initiatingPackageName = */ null, /* initiatingPackageSigningInfo = */ null,
+ /* originatingPackageName = */ null,
+ /* installingPackageName = */ INSTALLER_NAME_1);
+ assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1);
+ when(mIPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
+ ApplicationInfo appInfo = mock(ApplicationInfo.class);
+
+ when(mIPackageManager.getApplicationInfo(
+ eq(PACKAGE_NAME_1),
+ eq(0L),
+ anyInt())
+ ).thenReturn(appInfo);
+
+ long createTimestamp = PACKAGE_ADD_TIMESTAMP_1
+ - (System.currentTimeMillis() - SystemClock.uptimeMillis());
+ FieldSetter.setField(appInfo,
+ ApplicationInfo.class.getDeclaredField("createTimestamp"),
+ createTimestamp);
+
+ int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
+ assertEquals(USER_ID_1, UserHandle.getUserId(uid));
+
+ // The following 2 usage events generation is the only difference from the
+ // testHandleUsageEvent_packageAddedNoUsageEvent test.
+ // The 2 usage events make the package adding outside a time frame.
+ // Compared to testHandleUsageEvent_packageAddedInsideTimeFrame,
+ // it's a background install. Thus, it's not null for the return of
+ // mBackgroundInstallControlService.getBackgroundInstalledPackages()
+ doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
+ anyString(), anyString(), anyInt());
+ generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
+ USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
+ generateUsageEvent(Event.ACTIVITY_STOPPED,
+ USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3);
+
+ mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid);
+ mTestLooper.dispatchAll();
+
+ var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
+ assertNotNull(packages);
+ assertEquals(1, packages.size());
+ assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
+ }
+ @Test
+ public void testHandleUsageEvent_packageAddedOutsideTimeFrame2() throws
+ RemoteException, NoSuchFieldException {
+ assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+ InstallSourceInfo installSourceInfo = new InstallSourceInfo(
+ /* initiatingPackageName = */ null, /* initiatingPackageSigningInfo = */ null,
+ /* originatingPackageName = */ null,
+ /* installingPackageName = */ INSTALLER_NAME_1);
+ assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1);
+ when(mIPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
+ ApplicationInfo appInfo = mock(ApplicationInfo.class);
+
+ when(mIPackageManager.getApplicationInfo(
+ eq(PACKAGE_NAME_1),
+ eq(0L),
+ anyInt())
+ ).thenReturn(appInfo);
+
+ long createTimestamp = PACKAGE_ADD_TIMESTAMP_1
+ - (System.currentTimeMillis() - SystemClock.uptimeMillis());
+ FieldSetter.setField(appInfo,
+ ApplicationInfo.class.getDeclaredField("createTimestamp"),
+ createTimestamp);
+
+ int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
+ assertEquals(USER_ID_1, UserHandle.getUserId(uid));
+
+ // The following 2 usage events generation is the only difference from the
+ // testHandleUsageEvent_packageAddedNoUsageEvent test.
+ // These 2 usage events are triggered by INSTALLER_NAME_2.
+ // The 2 usage events make the package adding outside a time frame.
+ // Compared to testHandleUsageEvent_packageAddedInsideTimeFrame,
+ // it's a background install. Thus, it's not null for the return of
+ // mBackgroundInstallControlService.getBackgroundInstalledPackages()
+ doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
+ anyString(), anyString(), anyInt());
+ generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
+ USER_ID_2, INSTALLER_NAME_2, USAGE_EVENT_TIMESTAMP_2);
+ generateUsageEvent(Event.ACTIVITY_STOPPED,
+ USER_ID_2, INSTALLER_NAME_2, USAGE_EVENT_TIMESTAMP_3);
+
+ mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid);
+ mTestLooper.dispatchAll();
+
+ var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
+ assertNotNull(packages);
+ assertEquals(1, packages.size());
+ assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
+ }
+
+ @Test
+ public void testPackageRemoved() {
+ assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+ mBackgroundInstallControlService.initBackgroundInstalledPackages();
+ var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
+ assertNotNull(packages);
+
+ packages.add(USER_ID_1, PACKAGE_NAME_1);
+ packages.add(USER_ID_2, PACKAGE_NAME_2);
+
+ assertEquals(2, packages.size());
+ assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
+ assertTrue(packages.contains(USER_ID_2, PACKAGE_NAME_2));
+
+ int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
+ assertEquals(USER_ID_1, UserHandle.getUserId(uid));
+
+ mPackageListObserver.onPackageRemoved(PACKAGE_NAME_1, uid);
+ mTestLooper.dispatchAll();
+
+ assertEquals(1, packages.size());
+ assertFalse(packages.contains(USER_ID_1, PACKAGE_NAME_1));
+ assertTrue(packages.contains(USER_ID_2, PACKAGE_NAME_2));
+ }
+
+ @Test
+ public void testGetBackgroundInstalledPackages() throws RemoteException {
+ assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+ mBackgroundInstallControlService.initBackgroundInstalledPackages();
+ var bgPackages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
+ assertNotNull(bgPackages);
+
+ bgPackages.add(USER_ID_1, PACKAGE_NAME_1);
+ bgPackages.add(USER_ID_2, PACKAGE_NAME_2);
+
+ assertEquals(2, bgPackages.size());
+ assertTrue(bgPackages.contains(USER_ID_1, PACKAGE_NAME_1));
+ assertTrue(bgPackages.contains(USER_ID_2, PACKAGE_NAME_2));
+
+ List<PackageInfo> packages = new ArrayList<>();
+ var packageInfo1 = makePackageInfo(PACKAGE_NAME_1);
+ packages.add(packageInfo1);
+ var packageInfo2 = makePackageInfo(PACKAGE_NAME_2);
+ packages.add(packageInfo2);
+ var packageInfo3 = makePackageInfo(PACKAGE_NAME_3);
+ packages.add(packageInfo3);
+ doReturn(new ParceledListSlice<>(packages)).when(mIPackageManager).getInstalledPackages(
+ anyLong(), anyInt());
+
+ var resultPackages =
+ mBackgroundInstallControlService.getBackgroundInstalledPackages(0L, USER_ID_1);
+ assertEquals(1, resultPackages.getList().size());
+ assertTrue(resultPackages.getList().contains(packageInfo1));
+ assertFalse(resultPackages.getList().contains(packageInfo2));
+ assertFalse(resultPackages.getList().contains(packageInfo3));
+ }
+
+ /**
+ * Mock a usage event occurring.
+ *
+ * @param usageEventId id of a usage event
+ * @param userId user id of a usage event
+ * @param pkgName package name of a usage event
+ * @param timestamp timestamp of a usage event
+ */
+ private void generateUsageEvent(int usageEventId,
+ int userId,
+ String pkgName,
+ long timestamp) {
+ Event event = new Event(usageEventId, timestamp);
+ event.mPackage = pkgName;
+ mUsageEventListener.onUsageEvent(userId, event);
+ }
+
+ private PackageInfo makePackageInfo(String packageName) {
+ PackageInfo pkg = new PackageInfo();
+ pkg.packageName = packageName;
+ pkg.applicationInfo = new ApplicationInfo();
+ return pkg;
+ }
+
+ private class MockInjector implements BackgroundInstallControlService.Injector {
+ private final Context mContext;
+
+ MockInjector(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public IPackageManager getIPackageManager() {
+ return mIPackageManager;
+ }
+
+ @Override
+ public PackageManagerInternal getPackageManagerInternal() {
+ return mPackageManagerInternal;
+ }
+
+ @Override
+ public UsageStatsManagerInternal getUsageStatsManagerInternal() {
+ return mUsageStatsManagerInternal;
+ }
+
+ @Override
+ public PermissionManagerServiceInternal getPermissionManager() {
+ return mPermissionManager;
+ }
+
+ @Override
+ public Looper getLooper() {
+ return mLooper;
+ }
+
+ @Override
+ public File getDiskFile() {
+ return mFile;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java b/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java
index b034b0da387f..fe31b9cbe558 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java
@@ -257,7 +257,6 @@ public class PowerGroupTest {
.build();
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
- /* autoBrightness = */ false,
/* useProximitySensor= */ false,
/* boostScreenBrightness= */ false,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -273,7 +272,6 @@ public class PowerGroupTest {
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_DIM);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
- assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(false);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(false);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(false);
assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -297,7 +295,6 @@ public class PowerGroupTest {
mPowerGroup.setWakeLockSummaryLocked(WAKE_LOCK_DOZE);
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
- /* autoBrightness = */ true,
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -313,7 +310,6 @@ public class PowerGroupTest {
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_DOZE);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
- assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_ON);
@@ -336,7 +332,6 @@ public class PowerGroupTest {
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
- /* autoBrightness = */ true,
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -352,7 +347,6 @@ public class PowerGroupTest {
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_OFF);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
- assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -374,7 +368,6 @@ public class PowerGroupTest {
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
- /* autoBrightness = */ true,
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -390,7 +383,6 @@ public class PowerGroupTest {
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_OFF);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
- assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -412,7 +404,6 @@ public class PowerGroupTest {
mPowerGroup.sleepLocked(TIMESTAMP1, UID, GO_TO_SLEEP_REASON_TIMEOUT);
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
- /* autoBrightness = */ true,
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -428,7 +419,6 @@ public class PowerGroupTest {
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_OFF);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
- assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -451,7 +441,6 @@ public class PowerGroupTest {
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
mPowerGroup.setWakeLockSummaryLocked(WAKE_LOCK_SCREEN_BRIGHT);
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
- /* autoBrightness = */ true,
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -467,7 +456,6 @@ public class PowerGroupTest {
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
- assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -488,7 +476,6 @@ public class PowerGroupTest {
.build();
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
- /* autoBrightness = */ true,
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -504,7 +491,6 @@ public class PowerGroupTest {
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
- assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -526,7 +512,6 @@ public class PowerGroupTest {
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
mPowerGroup.setUserActivitySummaryLocked(USER_ACTIVITY_SCREEN_BRIGHT);
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
- /* autoBrightness = */ true,
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -542,7 +527,6 @@ public class PowerGroupTest {
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
- assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -563,7 +547,6 @@ public class PowerGroupTest {
.build();
assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
- /* autoBrightness = */ true,
/* useProximitySensor= */ true,
/* boostScreenBrightness= */ true,
/* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -579,7 +562,6 @@ public class PowerGroupTest {
mPowerGroup.mDisplayPowerRequest;
assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
- assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index 107bbe1c79e3..593ee4a7fa1a 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -45,6 +45,7 @@
<uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+ <uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION"/>
<!-- TODO: Remove largeHeap hack when memory leak is fixed (b/123984854) -->
<application android:debuggable="true"
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 982137b00bfc..f983fa9b49e3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -3284,7 +3284,7 @@ public class ActivityRecordTests extends WindowTestsBase {
assertFalse(app2.mActivityRecord.mImeInsetsFrozenUntilStartInput);
verify(app2.mClient, atLeastOnce()).resized(any(), anyBoolean(), any(),
insetsStateCaptor.capture(), anyBoolean(), anyBoolean(), anyInt(), anyInt(),
- anyBoolean());
+ anyInt());
assertFalse(app2.getInsetsState().getSource(ITYPE_IME).isVisible());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
index 49fd1ab60e09..92dd047b5537 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
@@ -52,6 +52,8 @@ import android.view.SurfaceControl;
import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
+import com.android.server.wm.ContentRecorder.MediaProjectionManagerWrapper;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -79,7 +81,7 @@ public class ContentRecorderTests extends WindowTestsBase {
private ContentRecordingSession mTaskSession;
private static Point sSurfaceSize;
private ContentRecorder mContentRecorder;
- @Mock private ContentRecorder.MediaProjectionManagerWrapper mMediaProjectionManagerWrapper;
+ @Mock private MediaProjectionManagerWrapper mMediaProjectionManagerWrapper;
private SurfaceControl mRecordedSurface;
// Handle feature flag.
private ConfigListener mConfigListener;
@@ -241,7 +243,7 @@ public class ContentRecorderTests extends WindowTestsBase {
}
@Test
- public void testOnTaskConfigurationChanged_resizesSurface() {
+ public void testOnTaskOrientationConfigurationChanged_resizesSurface() {
mContentRecorder.setContentRecordingSession(mTaskSession);
mContentRecorder.updateRecording();
@@ -256,6 +258,29 @@ public class ContentRecorderTests extends WindowTestsBase {
}
@Test
+ public void testOnTaskBoundsConfigurationChanged_notifiesCallback() {
+ final int recordedWidth = 333;
+ final int recordedHeight = 999;
+ // WHEN a recording is ongoing.
+ mContentRecorder.setContentRecordingSession(mTaskSession);
+ mContentRecorder.updateRecording();
+ assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+ // WHEN a configuration change arrives, and the recorded content is a different size.
+ mTask.setBounds(new Rect(0, 0, recordedWidth, recordedHeight));
+ mContentRecorder.onConfigurationChanged(mDefaultDisplay.getLastOrientation());
+ assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+ // THEN content in the captured DisplayArea is scaled to fit the surface size.
+ verify(mTransaction, atLeastOnce()).setMatrix(eq(mRecordedSurface), anyFloat(), eq(0f),
+ eq(0f),
+ anyFloat());
+ // THEN the resize callback is notified.
+ verify(mMediaProjectionManagerWrapper).notifyActiveProjectionCapturedContentResized(
+ recordedWidth, recordedHeight);
+ }
+
+ @Test
public void testPauseRecording_pausesRecording() {
mContentRecorder.setContentRecordingSession(mDisplaySession);
mContentRecorder.updateRecording();
@@ -324,6 +349,9 @@ public class ContentRecorderTests extends WindowTestsBase {
int scaledWidth = Math.round((float) displayAreaBounds.width() / xScale);
int xInset = (sSurfaceSize.x - scaledWidth) / 2;
verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface, xInset, 0);
+ // THEN the resize callback is notified.
+ verify(mMediaProjectionManagerWrapper).notifyActiveProjectionCapturedContentResized(
+ displayAreaBounds.width(), displayAreaBounds.height());
}
private static class RecordingTestToken extends Binder {
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 13ea99ae6fec..6877e4f8bfd3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -166,6 +166,114 @@ public class SizeCompatTests extends WindowTestsBase {
}
@Test
+ public void testApplyStrategyToTranslucentActivities() {
+ mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
+ setUpDisplaySizeWithApp(2000, 1000);
+ prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
+ mActivity.info.setMinAspectRatio(1.2f);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ // Translucent Activity
+ final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+ .setLaunchedFromUid(mActivity.getUid())
+ .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
+ .setMinAspectRatio(1.1f)
+ .setMaxAspectRatio(3f)
+ .build();
+ doReturn(false).when(translucentActivity).fillsParent();
+ mTask.addChild(translucentActivity);
+ // We check bounds
+ final Rect opaqueBounds = mActivity.getConfiguration().windowConfiguration.getBounds();
+ final Rect translucentRequestedBounds = translucentActivity.getRequestedOverrideBounds();
+ assertEquals(opaqueBounds, translucentRequestedBounds);
+ // We check orientation
+ final int translucentOrientation =
+ translucentActivity.getRequestedConfigurationOrientation();
+ assertEquals(ORIENTATION_PORTRAIT, translucentOrientation);
+ // We check aspect ratios
+ assertEquals(1.2f, translucentActivity.getMinAspectRatio(), 0.00001f);
+ assertEquals(1.5f, translucentActivity.getMaxAspectRatio(), 0.00001f);
+ }
+
+ @Test
+ public void testNotApplyStrategyToTranslucentActivitiesWithDifferentUid() {
+ mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
+ setUpDisplaySizeWithApp(2000, 1000);
+ prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
+ mActivity.info.setMinAspectRatio(1.2f);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ // Translucent Activity
+ final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+ .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
+ .setMinAspectRatio(1.1f)
+ .setMaxAspectRatio(3f)
+ .build();
+ doReturn(false).when(translucentActivity).fillsParent();
+ mTask.addChild(translucentActivity);
+ // We check bounds
+ final Rect opaqueBounds = mActivity.getConfiguration().windowConfiguration.getBounds();
+ final Rect translucentRequestedBounds = translucentActivity.getRequestedOverrideBounds();
+ assertNotEquals(opaqueBounds, translucentRequestedBounds);
+ }
+
+ @Test
+ public void testApplyStrategyToMultipleTranslucentActivities() {
+ mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
+ setUpDisplaySizeWithApp(2000, 1000);
+ prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
+ mActivity.info.setMinAspectRatio(1.2f);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ // Translucent Activity
+ final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+ .setLaunchedFromUid(mActivity.getUid())
+ .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
+ .setMinAspectRatio(1.1f)
+ .setMaxAspectRatio(3f)
+ .build();
+ doReturn(false).when(translucentActivity).fillsParent();
+ mTask.addChild(translucentActivity);
+ // We check bounds
+ final Rect opaqueBounds = mActivity.getConfiguration().windowConfiguration.getBounds();
+ final Rect translucentRequestedBounds = translucentActivity.getRequestedOverrideBounds();
+ assertEquals(opaqueBounds, translucentRequestedBounds);
+ // Launch another translucent activity
+ final ActivityRecord translucentActivity2 = new ActivityBuilder(mAtm)
+ .setLaunchedFromUid(mActivity.getUid())
+ .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
+ .build();
+ doReturn(false).when(translucentActivity2).fillsParent();
+ mTask.addChild(translucentActivity2);
+ // We check bounds
+ final Rect translucent2RequestedBounds = translucentActivity2.getRequestedOverrideBounds();
+ assertEquals(opaqueBounds, translucent2RequestedBounds);
+ }
+
+ @Test
+ public void testTranslucentActivitiesDontGoInSizeCompactMode() {
+ mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
+ setUpDisplaySizeWithApp(2800, 1400);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ prepareUnresizable(mActivity, -1f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
+ // Rotate to put activity in size compat mode.
+ rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
+ assertTrue(mActivity.inSizeCompatMode());
+ // Rotate back
+ rotateDisplay(mActivity.mDisplayContent, ROTATION_0);
+ assertFalse(mActivity.inSizeCompatMode());
+ // We launch a transparent activity
+ final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+ .setLaunchedFromUid(mActivity.getUid())
+ .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
+ .build();
+ doReturn(true).when(translucentActivity).fillsParent();
+ mTask.addChild(translucentActivity);
+ // It should not be in SCM
+ assertFalse(translucentActivity.inSizeCompatMode());
+ // We rotate again
+ rotateDisplay(translucentActivity.mDisplayContent, ROTATION_90);
+ assertFalse(translucentActivity.inSizeCompatMode());
+ }
+
+ @Test
public void testRestartProcessIfVisible() {
setUpDisplaySizeWithApp(1000, 2500);
doNothing().when(mSupervisor).scheduleRestartTimeout(mActivity);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
index 5e1fae095db8..7d9f29c0a63d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
@@ -20,6 +20,9 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
import android.platform.test.annotations.Presubmit;
import android.view.SurfaceControl;
@@ -31,12 +34,15 @@ import org.junit.Before;
import org.junit.Test;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
@SmallTest
@Presubmit
public class SurfaceSyncGroupTest {
+ private final Executor mExecutor = Runnable::run;
+
@Before
public void setup() {
SurfaceSyncGroup.setTransactionFactory(StubTransaction::new);
@@ -45,10 +51,11 @@ public class SurfaceSyncGroupTest {
@Test
public void testSyncOne() throws InterruptedException {
final CountDownLatch finishedLatch = new CountDownLatch(1);
- SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(transaction -> finishedLatch.countDown());
+ SurfaceSyncGroup syncGroup = new SurfaceSyncGroup();
+ syncGroup.addSyncCompleteCallback(mExecutor, finishedLatch::countDown);
SyncTarget syncTarget = new SyncTarget();
- syncGroup.addToSync(syncTarget);
- syncGroup.markSyncReady();
+ syncGroup.addToSync(syncTarget, false /* parentSyncGroupMerge */);
+ syncGroup.onTransactionReady(null);
syncTarget.onBufferReady();
@@ -59,15 +66,16 @@ public class SurfaceSyncGroupTest {
@Test
public void testSyncMultiple() throws InterruptedException {
final CountDownLatch finishedLatch = new CountDownLatch(1);
- SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(transaction -> finishedLatch.countDown());
+ SurfaceSyncGroup syncGroup = new SurfaceSyncGroup();
+ syncGroup.addSyncCompleteCallback(mExecutor, finishedLatch::countDown);
SyncTarget syncTarget1 = new SyncTarget();
SyncTarget syncTarget2 = new SyncTarget();
SyncTarget syncTarget3 = new SyncTarget();
- syncGroup.addToSync(syncTarget1);
- syncGroup.addToSync(syncTarget2);
- syncGroup.addToSync(syncTarget3);
- syncGroup.markSyncReady();
+ syncGroup.addToSync(syncTarget1, false /* parentSyncGroupMerge */);
+ syncGroup.addToSync(syncTarget2, false /* parentSyncGroupMerge */);
+ syncGroup.addToSync(syncTarget3, false /* parentSyncGroupMerge */);
+ syncGroup.onTransactionReady(null);
syncTarget1.onBufferReady();
assertNotEquals(0, finishedLatch.getCount());
@@ -83,35 +91,35 @@ public class SurfaceSyncGroupTest {
@Test
public void testAddSyncWhenSyncComplete() {
- final CountDownLatch finishedLatch = new CountDownLatch(1);
- SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(transaction -> finishedLatch.countDown());
+ SurfaceSyncGroup syncGroup = new SurfaceSyncGroup();
SyncTarget syncTarget1 = new SyncTarget();
SyncTarget syncTarget2 = new SyncTarget();
- assertTrue(syncGroup.addToSync(syncTarget1));
- syncGroup.markSyncReady();
+ assertTrue(syncGroup.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
+ syncGroup.onTransactionReady(null);
// Adding to a sync that has been completed is also invalid since the sync id has been
// cleared.
- assertFalse(syncGroup.addToSync(syncTarget2));
+ assertFalse(syncGroup.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
}
@Test
- public void testMultiplesyncGroups() throws InterruptedException {
+ public void testMultipleSyncGroups() throws InterruptedException {
final CountDownLatch finishedLatch1 = new CountDownLatch(1);
final CountDownLatch finishedLatch2 = new CountDownLatch(1);
- SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup(
- transaction -> finishedLatch1.countDown());
- SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup(
- transaction -> finishedLatch2.countDown());
+ SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup();
+ SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup();
+
+ syncGroup1.addSyncCompleteCallback(mExecutor, finishedLatch1::countDown);
+ syncGroup2.addSyncCompleteCallback(mExecutor, finishedLatch2::countDown);
SyncTarget syncTarget1 = new SyncTarget();
SyncTarget syncTarget2 = new SyncTarget();
- assertTrue(syncGroup1.addToSync(syncTarget1));
- assertTrue(syncGroup2.addToSync(syncTarget2));
- syncGroup1.markSyncReady();
- syncGroup2.markSyncReady();
+ assertTrue(syncGroup1.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
+ assertTrue(syncGroup2.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
+ syncGroup1.onTransactionReady(null);
+ syncGroup2.onTransactionReady(null);
syncTarget1.onBufferReady();
@@ -126,22 +134,23 @@ public class SurfaceSyncGroupTest {
}
@Test
- public void testMergeSync() throws InterruptedException {
+ public void testAddSyncGroup() throws InterruptedException {
final CountDownLatch finishedLatch1 = new CountDownLatch(1);
final CountDownLatch finishedLatch2 = new CountDownLatch(1);
- SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup(
- transaction -> finishedLatch1.countDown());
- SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup(
- transaction -> finishedLatch2.countDown());
+ SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup();
+ SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup();
+
+ syncGroup1.addSyncCompleteCallback(mExecutor, finishedLatch1::countDown);
+ syncGroup2.addSyncCompleteCallback(mExecutor, finishedLatch2::countDown);
SyncTarget syncTarget1 = new SyncTarget();
SyncTarget syncTarget2 = new SyncTarget();
- assertTrue(syncGroup1.addToSync(syncTarget1));
- assertTrue(syncGroup2.addToSync(syncTarget2));
- syncGroup1.markSyncReady();
- syncGroup2.merge(syncGroup1);
- syncGroup2.markSyncReady();
+ assertTrue(syncGroup1.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
+ assertTrue(syncGroup2.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
+ syncGroup1.onTransactionReady(null);
+ syncGroup2.addToSync(syncGroup1, false /* parentSyncGroupMerge */);
+ syncGroup2.onTransactionReady(null);
// Finish syncTarget2 first to test that the syncGroup is not complete until the merged sync
// is also done.
@@ -161,28 +170,29 @@ public class SurfaceSyncGroupTest {
}
@Test
- public void testMergeSyncAlreadyComplete() throws InterruptedException {
+ public void testAddSyncAlreadyComplete() throws InterruptedException {
final CountDownLatch finishedLatch1 = new CountDownLatch(1);
final CountDownLatch finishedLatch2 = new CountDownLatch(1);
- SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup(
- transaction -> finishedLatch1.countDown());
- SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup(
- transaction -> finishedLatch2.countDown());
+ SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup();
+ SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup();
+
+ syncGroup1.addSyncCompleteCallback(mExecutor, finishedLatch1::countDown);
+ syncGroup2.addSyncCompleteCallback(mExecutor, finishedLatch2::countDown);
SyncTarget syncTarget1 = new SyncTarget();
SyncTarget syncTarget2 = new SyncTarget();
- assertTrue(syncGroup1.addToSync(syncTarget1));
- assertTrue(syncGroup2.addToSync(syncTarget2));
- syncGroup1.markSyncReady();
+ assertTrue(syncGroup1.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
+ assertTrue(syncGroup2.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
+ syncGroup1.onTransactionReady(null);
syncTarget1.onBufferReady();
// The first sync will still get a callback when it's sync requirements are done.
finishedLatch1.await(5, TimeUnit.SECONDS);
assertEquals(0, finishedLatch1.getCount());
- syncGroup2.merge(syncGroup1);
- syncGroup2.markSyncReady();
+ syncGroup2.addToSync(syncGroup1, false /* parentSyncGroupMerge */);
+ syncGroup2.onTransactionReady(null);
syncTarget2.onBufferReady();
// Verify that the second sync will receive complete since the merged sync was already
@@ -191,18 +201,145 @@ public class SurfaceSyncGroupTest {
assertEquals(0, finishedLatch2.getCount());
}
- private static class SyncTarget implements SurfaceSyncGroup.SyncTarget {
- private SurfaceSyncGroup.TransactionReadyCallback mTransactionReadyCallback;
+ @Test
+ public void testAddSyncAlreadyInASync_NewSyncReadyFirst() throws InterruptedException {
+ final CountDownLatch finishedLatch1 = new CountDownLatch(1);
+ final CountDownLatch finishedLatch2 = new CountDownLatch(1);
+ SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup();
+ SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup();
- @Override
- public void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
- SurfaceSyncGroup.TransactionReadyCallback transactionReadyCallback) {
- mTransactionReadyCallback = transactionReadyCallback;
- }
+ syncGroup1.addSyncCompleteCallback(mExecutor, finishedLatch1::countDown);
+ syncGroup2.addSyncCompleteCallback(mExecutor, finishedLatch2::countDown);
+
+ SyncTarget syncTarget1 = new SyncTarget();
+ SyncTarget syncTarget2 = new SyncTarget();
+ SyncTarget syncTarget3 = new SyncTarget();
+
+ assertTrue(syncGroup1.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
+ assertTrue(syncGroup1.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
+
+ // Add syncTarget1 to syncGroup2 so it forces syncGroup1 into syncGroup2
+ assertTrue(syncGroup2.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
+ assertTrue(syncGroup2.addToSync(syncTarget3, false /* parentSyncGroupMerge */));
+
+ syncGroup1.onTransactionReady(null);
+ syncGroup2.onTransactionReady(null);
+
+ // Make target1 and target3 ready, but not target2. SyncGroup2 should not be ready since
+ // SyncGroup2 also waits for all of SyncGroup1 to finish, which includes target2
+ syncTarget1.onBufferReady();
+ syncTarget3.onBufferReady();
+
+ // Neither SyncGroup will be ready.
+ finishedLatch1.await(1, TimeUnit.SECONDS);
+ finishedLatch2.await(1, TimeUnit.SECONDS);
+
+ assertEquals(1, finishedLatch1.getCount());
+ assertEquals(1, finishedLatch2.getCount());
+
+ syncTarget2.onBufferReady();
+
+ // Both sync groups should be ready after target2 completed.
+ finishedLatch1.await(5, TimeUnit.SECONDS);
+ finishedLatch2.await(5, TimeUnit.SECONDS);
+ assertEquals(0, finishedLatch1.getCount());
+ assertEquals(0, finishedLatch2.getCount());
+ }
+
+ @Test
+ public void testAddSyncAlreadyInASync_OldSyncFinishesFirst() throws InterruptedException {
+ final CountDownLatch finishedLatch1 = new CountDownLatch(1);
+ final CountDownLatch finishedLatch2 = new CountDownLatch(1);
+ SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup();
+ SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup();
+
+ syncGroup1.addSyncCompleteCallback(mExecutor, finishedLatch1::countDown);
+ syncGroup2.addSyncCompleteCallback(mExecutor, finishedLatch2::countDown);
+
+ SyncTarget syncTarget1 = new SyncTarget();
+ SyncTarget syncTarget2 = new SyncTarget();
+ SyncTarget syncTarget3 = new SyncTarget();
+
+ assertTrue(syncGroup1.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
+ assertTrue(syncGroup1.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
+ syncTarget2.onBufferReady();
+
+ // Add syncTarget1 to syncGroup2 so it forces syncGroup1 into syncGroup2
+ assertTrue(syncGroup2.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
+ assertTrue(syncGroup2.addToSync(syncTarget3, false /* parentSyncGroupMerge */));
+
+ syncGroup1.onTransactionReady(null);
+ syncGroup2.onTransactionReady(null);
+
+ syncTarget1.onBufferReady();
+
+ // Only SyncGroup1 will be ready, but SyncGroup2 still needs its own targets to be ready.
+ finishedLatch1.await(1, TimeUnit.SECONDS);
+ finishedLatch2.await(1, TimeUnit.SECONDS);
+
+ assertEquals(0, finishedLatch1.getCount());
+ assertEquals(1, finishedLatch2.getCount());
+
+ syncTarget3.onBufferReady();
+
+ // SyncGroup2 is finished after target3 completed.
+ finishedLatch2.await(1, TimeUnit.SECONDS);
+ assertEquals(0, finishedLatch2.getCount());
+ }
+
+ @Test
+ public void testParentSyncGroupMerge_true() {
+ // Temporarily set a new transaction factory so it will return the stub transaction for
+ // the sync group.
+ SurfaceControl.Transaction parentTransaction = spy(new StubTransaction());
+ SurfaceSyncGroup.setTransactionFactory(() -> parentTransaction);
+
+ final CountDownLatch finishedLatch = new CountDownLatch(1);
+ SurfaceSyncGroup syncGroup = new SurfaceSyncGroup();
+ syncGroup.addSyncCompleteCallback(mExecutor, finishedLatch::countDown);
+
+ SurfaceControl.Transaction targetTransaction = spy(new StubTransaction());
+ SurfaceSyncGroup.setTransactionFactory(() -> targetTransaction);
+
+ SyncTarget syncTarget = new SyncTarget();
+ assertTrue(syncGroup.addToSync(syncTarget, true /* parentSyncGroupMerge */));
+ syncTarget.onTransactionReady(null);
+
+ // When parentSyncGroupMerge is true, the transaction passed in merges the main SyncGroup
+ // transaction first because it knows the previous parentSyncGroup is older so it should
+ // be overwritten by anything newer.
+ verify(targetTransaction).merge(parentTransaction);
+ verify(parentTransaction).merge(targetTransaction);
+ }
+
+ @Test
+ public void testParentSyncGroupMerge_false() {
+ // Temporarily set a new transaction factory so it will return the stub transaction for
+ // the sync group.
+ SurfaceControl.Transaction parentTransaction = spy(new StubTransaction());
+ SurfaceSyncGroup.setTransactionFactory(() -> parentTransaction);
+
+ final CountDownLatch finishedLatch = new CountDownLatch(1);
+ SurfaceSyncGroup syncGroup = new SurfaceSyncGroup();
+ syncGroup.addSyncCompleteCallback(mExecutor, finishedLatch::countDown);
+
+ SurfaceControl.Transaction targetTransaction = spy(new StubTransaction());
+ SurfaceSyncGroup.setTransactionFactory(() -> targetTransaction);
+
+ SyncTarget syncTarget = new SyncTarget();
+ assertTrue(syncGroup.addToSync(syncTarget, false /* parentSyncGroupMerge */));
+ syncTarget.onTransactionReady(null);
+
+ // When parentSyncGroupMerge is false, the transaction passed in should not merge
+ // the main SyncGroup since we don't need to change the transaction order
+ verify(targetTransaction, never()).merge(parentTransaction);
+ verify(parentTransaction).merge(targetTransaction);
+ }
+ private static class SyncTarget extends SurfaceSyncGroup {
void onBufferReady() {
SurfaceControl.Transaction t = new StubTransaction();
- mTransactionReadyCallback.onTransactionReady(t);
+ onTransactionReady(t);
}
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
index 3f8acc651110..6e72bf360295 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
@@ -46,7 +46,7 @@ public class TestIWindow extends IWindow.Stub {
@Override
public void resized(ClientWindowFrames frames, boolean reportDraw,
MergedConfiguration mergedConfig, InsetsState insetsState, boolean forceLayout,
- boolean alwaysConsumeSystemBars, int displayId, int seqId, boolean dragResizing)
+ boolean alwaysConsumeSystemBars, int displayId, int seqId, int resizeMode)
throws RemoteException {
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 183ccceec4f4..69e3244af1b6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -276,9 +276,12 @@ public class WindowStateTests extends WindowTestsBase {
assertFalse(imeWindow.canBeImeTarget());
// Simulate the window is in split screen root task.
+ final DockedTaskDividerController controller =
+ mDisplayContent.getDockedDividerController();
final Task rootTask = createTask(mDisplayContent,
WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
spyOn(appWindow);
+ spyOn(controller);
spyOn(rootTask);
rootTask.setFocusable(false);
doReturn(rootTask).when(appWindow).getRootTask();
@@ -772,7 +775,7 @@ public class WindowStateTests extends WindowTestsBase {
anyBoolean() /* reportDraw */, any() /* mergedConfig */,
any() /* insetsState */, anyBoolean() /* forceLayout */,
anyBoolean() /* alwaysConsumeSystemBars */, anyInt() /* displayId */,
- anyInt() /* seqId */, anyBoolean() /* dragResizing */);
+ anyInt() /* seqId */, anyInt() /* resizeMode */);
} catch (RemoteException ignored) {
}
win.reportResized();
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/CoordinateTransformsTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/CoordinateTransformsTest.java
index 99ceb2011db3..3e74626b66d7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/utils/CoordinateTransformsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/CoordinateTransformsTest.java
@@ -21,6 +21,7 @@ import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
+import static com.android.server.wm.utils.CoordinateTransforms.computeRotationMatrix;
import static com.android.server.wm.utils.CoordinateTransforms.transformLogicalToPhysicalCoordinates;
import static com.android.server.wm.utils.CoordinateTransforms.transformPhysicalToLogicalCoordinates;
import static com.android.server.wm.utils.CoordinateTransforms.transformToRotation;
@@ -185,6 +186,44 @@ public class CoordinateTransformsTest {
assertEquals(mMatrix2, mMatrix);
}
+ @Test
+ public void rotate_0_bottomRight() {
+ computeRotationMatrix(ROTATION_0, W, H, mMatrix);
+ PointF newPoints = checkMappedPoints(W, H);
+ assertEquals(W, newPoints.x, 0);
+ assertEquals(H, newPoints.y, 0);
+ }
+
+ @Test
+ public void rotate_90_bottomRight() {
+ computeRotationMatrix(ROTATION_90, W, H, mMatrix);
+ PointF newPoints = checkMappedPoints(W, H);
+ assertEquals(0, newPoints.x, 0);
+ assertEquals(W, newPoints.y, 0);
+ }
+
+ @Test
+ public void rotate_180_bottomRight() {
+ computeRotationMatrix(ROTATION_180, W, H, mMatrix);
+ PointF newPoints = checkMappedPoints(W, H);
+ assertEquals(0, newPoints.x, 0);
+ assertEquals(0, newPoints.y, 0);
+ }
+
+ @Test
+ public void rotate_270_bottomRight() {
+ computeRotationMatrix(ROTATION_270, W, H, mMatrix);
+ PointF newPoints = checkMappedPoints(W, H);
+ assertEquals(H, newPoints.x, 0);
+ assertEquals(0, newPoints.y, 0);
+ }
+
+ private PointF checkMappedPoints(int x, int y) {
+ final float[] fs = new float[] {x, y};
+ mMatrix.mapPoints(fs);
+ return new PointF(fs[0], fs[1]);
+ }
+
private void assertMatricesAreInverses(Matrix matrix, Matrix matrix2) {
final Matrix concat = new Matrix();
concat.setConcat(matrix, matrix2);
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/RotationAnimationUtilsTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/RotationAnimationUtilsTest.java
index cd4d65d7dab1..ff43ff718220 100644
--- a/services/tests/wmtests/src/com/android/server/wm/utils/RotationAnimationUtilsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/RotationAnimationUtilsTest.java
@@ -23,15 +23,11 @@ import static org.junit.Assert.assertEquals;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.ColorSpace;
-import android.graphics.Matrix;
-import android.graphics.PointF;
import android.hardware.HardwareBuffer;
import android.platform.test.annotations.Presubmit;
-import android.view.Surface;
import com.android.internal.policy.TransitionAnimation;
-import org.junit.Before;
import org.junit.Test;
@Presubmit
@@ -39,16 +35,8 @@ public class RotationAnimationUtilsTest {
private static final int BITMAP_HEIGHT = 100;
private static final int BITMAP_WIDTH = 100;
- private static final int POINT_WIDTH = 1000;
- private static final int POINT_HEIGHT = 2000;
private ColorSpace mColorSpace = ColorSpace.get(ColorSpace.Named.DISPLAY_P3);
- private Matrix mMatrix;
-
- @Before
- public void setup() {
- mMatrix = new Matrix();
- }
@Test
public void blackLuma() {
@@ -93,48 +81,6 @@ public class RotationAnimationUtilsTest {
assertEquals(1, borderLuma, 0);
}
- @Test
- public void rotate_0_bottomRight() {
- RotationAnimationUtils.createRotationMatrix(Surface.ROTATION_0,
- POINT_WIDTH, POINT_HEIGHT, mMatrix);
- PointF newPoints = checkMappedPoints(POINT_WIDTH, POINT_HEIGHT);
- assertEquals(POINT_WIDTH, newPoints.x, 0);
- assertEquals(POINT_HEIGHT, newPoints.y, 0);
- }
-
- @Test
- public void rotate_90_bottomRight() {
- RotationAnimationUtils.createRotationMatrix(Surface.ROTATION_90,
- POINT_WIDTH, POINT_HEIGHT, mMatrix);
- PointF newPoints = checkMappedPoints(POINT_WIDTH, POINT_HEIGHT);
- assertEquals(0, newPoints.x, 0);
- assertEquals(POINT_WIDTH, newPoints.y, 0);
- }
-
- @Test
- public void rotate_180_bottomRight() {
- RotationAnimationUtils.createRotationMatrix(Surface.ROTATION_180,
- POINT_WIDTH, POINT_HEIGHT, mMatrix);
- PointF newPoints = checkMappedPoints(POINT_WIDTH, POINT_HEIGHT);
- assertEquals(0, newPoints.x, 0);
- assertEquals(0, newPoints.y, 0);
- }
-
- @Test
- public void rotate_270_bottomRight() {
- RotationAnimationUtils.createRotationMatrix(Surface.ROTATION_270,
- POINT_WIDTH, POINT_HEIGHT, mMatrix);
- PointF newPoints = checkMappedPoints(POINT_WIDTH, POINT_HEIGHT);
- assertEquals(POINT_HEIGHT, newPoints.x, 0);
- assertEquals(0, newPoints.y, 0);
- }
-
- private PointF checkMappedPoints(int x, int y) {
- final float[] fs = new float[] {x, y};
- mMatrix.mapPoints(fs);
- return new PointF(fs[0], fs[1]);
- }
-
private Bitmap createBitmap(float luma) {
return createBitmap(luma, BITMAP_WIDTH, BITMAP_HEIGHT);
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
index 9375b59d9123..84bd71601643 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
@@ -28,6 +28,7 @@ import android.annotation.NonNull;
import android.content.Context;
import android.hardware.soundtrigger.SoundTrigger;
import android.media.permission.Identity;
+import android.os.IBinder;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.SharedMemory;
@@ -74,12 +75,13 @@ final class DspTrustedHotwordDetectorSession extends HotwordDetectorSession {
DspTrustedHotwordDetectorSession(
@NonNull HotwordDetectionConnection.ServiceConnection remoteHotwordDetectionService,
- @NonNull Object lock, @NonNull Context context,
+ @NonNull Object lock, @NonNull Context context, @NonNull IBinder token,
@NonNull IHotwordRecognitionStatusCallback callback, int voiceInteractionServiceUid,
Identity voiceInteractorIdentity,
@NonNull ScheduledExecutorService scheduledExecutorService, boolean logging) {
- super(remoteHotwordDetectionService, lock, context, callback, voiceInteractionServiceUid,
- voiceInteractorIdentity, scheduledExecutorService, logging);
+ super(remoteHotwordDetectionService, lock, context, token, callback,
+ voiceInteractionServiceUid, voiceInteractorIdentity, scheduledExecutorService,
+ logging);
}
@SuppressWarnings("GuardedBy")
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 411bad601bf4..6a7a2f98d481 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -49,11 +49,12 @@ import android.os.SharedMemory;
import android.provider.DeviceConfig;
import android.service.voice.HotwordDetectionService;
import android.service.voice.HotwordDetector;
-import android.service.voice.IHotwordDetectionService;
import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
+import android.service.voice.ISandboxedDetectionService;
import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity;
import android.speech.IRecognitionServiceManager;
import android.util.Slog;
+import android.util.SparseArray;
import android.view.contentcapture.IContentCaptureManager;
import com.android.internal.annotations.GuardedBy;
@@ -68,6 +69,7 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
import java.util.function.Function;
/**
@@ -113,18 +115,18 @@ final class HotwordDetectionConnection {
@GuardedBy("mLock")
private boolean mDebugHotwordLogging = false;
+ /**
+ * For multiple detectors feature, we only support one AlwaysOnHotwordDetector and one
+ * SoftwareHotwordDetector at the same time. We use SparseArray with detector type as the key
+ * to record the detectors.
+ */
@GuardedBy("mLock")
- private final HotwordDetectorSession mHotwordDetectorSession;
+ private final SparseArray<HotwordDetectorSession> mHotwordDetectorSessions =
+ new SparseArray<>();
HotwordDetectionConnection(Object lock, Context context, int voiceInteractionServiceUid,
Identity voiceInteractorIdentity, ComponentName serviceName, int userId,
- boolean bindInstantServiceAllowed, @Nullable PersistableBundle options,
- @Nullable SharedMemory sharedMemory,
- @NonNull IHotwordRecognitionStatusCallback callback, int detectorType) {
- if (callback == null) {
- Slog.w(TAG, "Callback is null while creating connection");
- throw new IllegalArgumentException("Callback is null while creating connection");
- }
+ boolean bindInstantServiceAllowed, int detectorType) {
mLock = lock;
mContext = context;
mVoiceInteractionServiceUid = voiceInteractionServiceUid;
@@ -142,19 +144,6 @@ final class HotwordDetectionConnection {
mRemoteHotwordDetectionService = mServiceConnectionFactory.createLocked();
mLastRestartInstant = Instant.now();
- if (detectorType == HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP) {
- mHotwordDetectorSession = new DspTrustedHotwordDetectorSession(
- mRemoteHotwordDetectionService, mLock, mContext, callback,
- mVoiceInteractionServiceUid, mVoiceInteractorIdentity,
- mScheduledExecutorService, mDebugHotwordLogging);
- } else {
- mHotwordDetectorSession = new SoftwareTrustedHotwordDetectorSession(
- mRemoteHotwordDetectionService, mLock, mContext, callback,
- mVoiceInteractionServiceUid, mVoiceInteractorIdentity,
- mScheduledExecutorService, mDebugHotwordLogging);
- }
- mHotwordDetectorSession.initialize(options, sharedMemory);
-
if (mReStartPeriodSeconds <= 0) {
mCancellationTaskFuture = null;
} else {
@@ -210,7 +199,10 @@ final class HotwordDetectionConnection {
void cancelLocked() {
Slog.v(TAG, "cancelLocked");
clearDebugHotwordLoggingTimeoutLocked();
- mHotwordDetectorSession.destroyLocked();
+ runForEachHotwordDetectorSessionLocked((session) -> {
+ session.destroyLocked();
+ });
+ mHotwordDetectorSessions.clear();
mDebugHotwordLogging = false;
mRemoteHotwordDetectionService.unbind();
LocalServices.getService(PermissionManagerServiceInternal.class)
@@ -220,7 +212,7 @@ final class HotwordDetectionConnection {
}
mIdentity = null;
if (mCancellationTaskFuture != null) {
- mCancellationTaskFuture.cancel(/* may interrupt */ true);
+ mCancellationTaskFuture.cancel(/* mayInterruptIfRunning= */ true);
}
if (mAudioFlinger != null) {
mAudioFlinger.unlinkToDeath(mAudioServerDeathRecipient, /* flags= */ 0);
@@ -228,57 +220,65 @@ final class HotwordDetectionConnection {
}
@SuppressWarnings("GuardedBy")
- void updateStateLocked(PersistableBundle options, SharedMemory sharedMemory) {
- mHotwordDetectorSession.updateStateLocked(options, sharedMemory, mLastRestartInstant);
+ void updateStateLocked(PersistableBundle options, SharedMemory sharedMemory,
+ @NonNull IBinder token) {
+ final HotwordDetectorSession session = getDetectorSessionByTokenLocked(token);
+ if (session == null) {
+ Slog.v(TAG, "Not found the detector by token");
+ return;
+ }
+ session.updateStateLocked(options, sharedMemory, mLastRestartInstant);
}
/**
* This method is only used by SoftwareHotwordDetector.
*/
- void startListeningFromMic(
+ void startListeningFromMicLocked(
AudioFormat audioFormat,
IMicrophoneHotwordDetectionVoiceInteractionCallback callback) {
if (DEBUG) {
- Slog.d(TAG, "startListeningFromMic");
+ Slog.d(TAG, "startListeningFromMicLocked");
}
- synchronized (mLock) {
- if (!(mHotwordDetectorSession instanceof SoftwareTrustedHotwordDetectorSession)) {
- Slog.d(TAG, "It is not a software detector");
- return;
- }
- ((SoftwareTrustedHotwordDetectorSession) mHotwordDetectorSession)
- .startListeningFromMicLocked(audioFormat, callback);
+ // We only support one Dsp trusted hotword detector and one software hotword detector at
+ // the same time, so we can reuse original single software trusted hotword mechanism.
+ final SoftwareTrustedHotwordDetectorSession session =
+ getSoftwareTrustedHotwordDetectorSessionLocked();
+ if (session == null) {
+ return;
}
+ session.startListeningFromMicLocked(audioFormat, callback);
}
- public void startListeningFromExternalSource(
+ public void startListeningFromExternalSourceLocked(
ParcelFileDescriptor audioStream,
AudioFormat audioFormat,
@Nullable PersistableBundle options,
+ @NonNull IBinder token,
IMicrophoneHotwordDetectionVoiceInteractionCallback callback) {
if (DEBUG) {
- Slog.d(TAG, "startListeningFromExternalSource");
+ Slog.d(TAG, "startListeningFromExternalSourceLocked");
}
- synchronized (mLock) {
- mHotwordDetectorSession.startListeningFromExternalSourceLocked(audioStream, audioFormat,
- options, callback);
+ final HotwordDetectorSession session = getDetectorSessionByTokenLocked(token);
+ if (session == null) {
+ Slog.v(TAG, "Not found the detector by token");
+ return;
}
+ session.startListeningFromExternalSourceLocked(audioStream, audioFormat, options, callback);
}
/**
* This method is only used by SoftwareHotwordDetector.
*/
- void stopListening() {
+ void stopListeningFromMicLocked() {
if (DEBUG) {
- Slog.d(TAG, "stopListening");
+ Slog.d(TAG, "stopListeningFromMicLocked");
}
- synchronized (mLock) {
- if (!(mHotwordDetectorSession instanceof SoftwareTrustedHotwordDetectorSession)) {
- Slog.d(TAG, "It is not a software detector");
- return;
- }
- ((SoftwareTrustedHotwordDetectorSession) mHotwordDetectorSession).stopListeningLocked();
+ final SoftwareTrustedHotwordDetectorSession session =
+ getSoftwareTrustedHotwordDetectorSessionLocked();
+ if (session == null) {
+ return;
}
+ session.stopListeningFromMicLocked();
}
void triggerHardwareRecognitionEventForTestLocked(
@@ -295,13 +295,16 @@ final class HotwordDetectionConnection {
if (DEBUG) {
Slog.d(TAG, "detectFromDspSource");
}
+ // We only support one Dsp trusted hotword detector and one software hotword detector at
+ // the same time, so we can reuse original single Dsp trusted hotword mechanism.
synchronized (mLock) {
- if (!(mHotwordDetectorSession instanceof DspTrustedHotwordDetectorSession)) {
- Slog.d(TAG, "It is not a Dsp detector");
+ final DspTrustedHotwordDetectorSession session =
+ getDspTrustedHotwordDetectorSessionLocked();
+ if (session == null || !session.isSameCallback(externalCallback)) {
+ Slog.v(TAG, "Not found the Dsp detector by callback");
return;
}
- ((DspTrustedHotwordDetectorSession) mHotwordDetectorSession).detectFromDspSourceLocked(
- recognitionEvent, externalCallback);
+ session.detectFromDspSourceLocked(recognitionEvent, externalCallback);
}
}
@@ -317,7 +320,9 @@ final class HotwordDetectionConnection {
Slog.v(TAG, "setDebugHotwordLoggingLocked: " + logging);
clearDebugHotwordLoggingTimeoutLocked();
mDebugHotwordLogging = logging;
- mHotwordDetectorSession.setDebugHotwordLoggingLocked(logging);
+ runForEachHotwordDetectorSessionLocked((session) -> {
+ session.setDebugHotwordLoggingLocked(logging);
+ });
if (logging) {
// Reset mDebugHotwordLogging to false after one hour
@@ -325,7 +330,9 @@ final class HotwordDetectionConnection {
Slog.v(TAG, "Timeout to reset mDebugHotwordLogging to false");
synchronized (mLock) {
mDebugHotwordLogging = false;
- mHotwordDetectorSession.setDebugHotwordLoggingLocked(false);
+ runForEachHotwordDetectorSessionLocked((session) -> {
+ session.setDebugHotwordLoggingLocked(false);
+ });
}
}, RESET_DEBUG_HOTWORD_LOGGING_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
}
@@ -333,7 +340,7 @@ final class HotwordDetectionConnection {
private void clearDebugHotwordLoggingTimeoutLocked() {
if (mDebugHotwordLoggingTimeoutFuture != null) {
- mDebugHotwordLoggingTimeoutFuture.cancel(/* mayInterruptIfRunning= */true);
+ mDebugHotwordLoggingTimeoutFuture.cancel(/* mayInterruptIfRunning= */ true);
mDebugHotwordLoggingTimeoutFuture = null;
}
}
@@ -350,12 +357,14 @@ final class HotwordDetectionConnection {
mRemoteHotwordDetectionService = mServiceConnectionFactory.createLocked();
Slog.v(TAG, "Started the new process, dispatching processRestarted to detector");
- mHotwordDetectorSession.updateRemoteHotwordDetectionServiceLocked(
- mRemoteHotwordDetectionService);
- mHotwordDetectorSession.informRestartProcessLocked();
+ runForEachHotwordDetectorSessionLocked((session) -> {
+ session.updateRemoteHotwordDetectionServiceLocked(mRemoteHotwordDetectionService);
+ session.informRestartProcessLocked();
+ });
if (DEBUG) {
Slog.i(TAG, "processRestarted is dispatched done, unbinding from the old process");
}
+
oldConnection.ignoreConnectionStatusEvents();
oldConnection.unbind();
if (previousIdentity != null) {
@@ -431,8 +440,10 @@ final class HotwordDetectionConnection {
pw.print(prefix); pw.print("mLastRestartInstant="); pw.println(mLastRestartInstant);
pw.print(prefix); pw.print("mDetectorType=");
pw.println(HotwordDetector.detectorTypeToString(mDetectorType));
- pw.print(prefix); pw.println("HotwordDetectorSession");
- mHotwordDetectorSession.dumpLocked(prefix, pw);
+ pw.print(prefix); pw.println("HotwordDetectorSession(s)");
+ runForEachHotwordDetectorSessionLocked((session) -> {
+ session.dumpLocked(prefix, pw);
+ });
}
}
@@ -450,7 +461,7 @@ final class HotwordDetectionConnection {
ServiceConnection createLocked() {
ServiceConnection connection =
new ServiceConnection(mContext, mIntent, mBindingFlags, mUser,
- IHotwordDetectionService.Stub::asInterface,
+ ISandboxedDetectionService.Stub::asInterface,
mRestartCount++ % MAX_ISOLATED_PROCESS_NUMBER);
connection.connect();
@@ -462,7 +473,7 @@ final class HotwordDetectionConnection {
}
}
- class ServiceConnection extends ServiceConnector.Impl<IHotwordDetectionService> {
+ class ServiceConnection extends ServiceConnector.Impl<ISandboxedDetectionService> {
private final Object mLock = new Object();
private final Intent mIntent;
@@ -475,7 +486,7 @@ final class HotwordDetectionConnection {
ServiceConnection(@NonNull Context context,
@NonNull Intent intent, int bindingFlags, int userId,
- @Nullable Function<IBinder, IHotwordDetectionService> binderAsInterface,
+ @Nullable Function<IBinder, ISandboxedDetectionService> binderAsInterface,
int instanceNumber) {
super(context, intent, bindingFlags, userId, binderAsInterface);
this.mIntent = intent;
@@ -484,7 +495,7 @@ final class HotwordDetectionConnection {
}
@Override // from ServiceConnector.Impl
- protected void onServiceConnectionStatusChanged(IHotwordDetectionService service,
+ protected void onServiceConnectionStatusChanged(ISandboxedDetectionService service,
boolean connected) {
if (DEBUG) {
Slog.d(TAG, "onServiceConnectionStatusChanged connected = " + connected);
@@ -525,8 +536,10 @@ final class HotwordDetectionConnection {
}
}
synchronized (HotwordDetectionConnection.this.mLock) {
- mHotwordDetectorSession.reportErrorLocked(
- HotwordDetectorSession.HOTWORD_DETECTION_SERVICE_DIED);
+ runForEachHotwordDetectorSessionLocked((session) -> {
+ session.reportErrorLocked(
+ HotwordDetectorSession.HOTWORD_DETECTION_SERVICE_DIED);
+ });
}
}
@@ -571,6 +584,93 @@ final class HotwordDetectionConnection {
}
}
+ @SuppressWarnings("GuardedBy")
+ void createDetectorLocked(
+ @Nullable PersistableBundle options,
+ @Nullable SharedMemory sharedMemory,
+ @NonNull IBinder token,
+ @NonNull IHotwordRecognitionStatusCallback callback,
+ int detectorType) {
+ // We only support one Dsp trusted hotword detector and one software hotword detector at
+ // the same time, remove existing one.
+ HotwordDetectorSession removeSession = mHotwordDetectorSessions.get(detectorType);
+ if (removeSession != null) {
+ removeSession.destroyLocked();
+ mHotwordDetectorSessions.remove(detectorType);
+ }
+ final HotwordDetectorSession session;
+ if (detectorType == HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP) {
+ session = new DspTrustedHotwordDetectorSession(mRemoteHotwordDetectionService,
+ mLock, mContext, token, callback, mVoiceInteractionServiceUid,
+ mVoiceInteractorIdentity, mScheduledExecutorService, mDebugHotwordLogging);
+ } else {
+ session = new SoftwareTrustedHotwordDetectorSession(
+ mRemoteHotwordDetectionService, mLock, mContext, token, callback,
+ mVoiceInteractionServiceUid, mVoiceInteractorIdentity,
+ mScheduledExecutorService, mDebugHotwordLogging);
+ }
+ mHotwordDetectorSessions.put(detectorType, session);
+ session.initialize(options, sharedMemory);
+ }
+
+ @SuppressWarnings("GuardedBy")
+ void destroyDetectorLocked(@NonNull IBinder token) {
+ final HotwordDetectorSession session = getDetectorSessionByTokenLocked(token);
+ if (session != null) {
+ session.destroyLocked();
+ final int index = mHotwordDetectorSessions.indexOfValue(session);
+ if (index < 0 || index > mHotwordDetectorSessions.size() - 1) {
+ return;
+ }
+ mHotwordDetectorSessions.removeAt(index);
+ }
+ }
+
+ @SuppressWarnings("GuardedBy")
+ private HotwordDetectorSession getDetectorSessionByTokenLocked(IBinder token) {
+ if (token == null) {
+ return null;
+ }
+ for (int i = 0; i < mHotwordDetectorSessions.size(); i++) {
+ final HotwordDetectorSession session = mHotwordDetectorSessions.valueAt(i);
+ if (!session.isDestroyed() && session.isSameToken(token)) {
+ return session;
+ }
+ }
+ return null;
+ }
+
+ @SuppressWarnings("GuardedBy")
+ private DspTrustedHotwordDetectorSession getDspTrustedHotwordDetectorSessionLocked() {
+ final HotwordDetectorSession session = mHotwordDetectorSessions.get(
+ HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP);
+ if (session == null || session.isDestroyed()) {
+ Slog.v(TAG, "Not found the Dsp detector");
+ return null;
+ }
+ return (DspTrustedHotwordDetectorSession) session;
+ }
+
+ @SuppressWarnings("GuardedBy")
+ private SoftwareTrustedHotwordDetectorSession getSoftwareTrustedHotwordDetectorSessionLocked() {
+ final HotwordDetectorSession session = mHotwordDetectorSessions.get(
+ HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE);
+ if (session == null || session.isDestroyed()) {
+ Slog.v(TAG, "Not found the software detector");
+ return null;
+ }
+ return (SoftwareTrustedHotwordDetectorSession) session;
+ }
+
+ @SuppressWarnings("GuardedBy")
+ private void runForEachHotwordDetectorSessionLocked(
+ @NonNull Consumer<HotwordDetectorSession> action) {
+ for (int i = 0; i < mHotwordDetectorSessions.size(); i++) {
+ HotwordDetectorSession session = mHotwordDetectorSessions.valueAt(i);
+ action.accept(session);
+ }
+ }
+
private static void updateAudioFlinger(ServiceConnection connection, IBinder audioFlinger) {
// TODO: Consider using a proxy that limits the exposed API surface.
connection.run(service -> service.updateAudioFlinger(audioFlinger));
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectorSession.java
index 742c324fa66f..689423ad3e3e 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectorSession.java
@@ -57,6 +57,7 @@ import android.media.permission.Identity;
import android.media.permission.PermissionUtil;
import android.os.Binder;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.IRemoteCallback;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
@@ -182,16 +183,18 @@ abstract class HotwordDetectorSession {
private boolean mDestroyed = false;
@GuardedBy("mLock")
boolean mPerformingExternalSourceHotwordDetection;
+ @NonNull final IBinder mToken;
HotwordDetectorSession(
@NonNull HotwordDetectionConnection.ServiceConnection remoteHotwordDetectionService,
- @NonNull Object lock, @NonNull Context context,
+ @NonNull Object lock, @NonNull Context context, @NonNull IBinder token,
@NonNull IHotwordRecognitionStatusCallback callback, int voiceInteractionServiceUid,
Identity voiceInteractorIdentity,
@NonNull ScheduledExecutorService scheduledExecutorService, boolean logging) {
mRemoteHotwordDetectionService = remoteHotwordDetectionService;
mLock = lock;
mContext = context;
+ mToken = token;
mCallback = callback;
mVoiceInteractionServiceUid = voiceInteractionServiceUid;
mVoiceInteractorIdentity = voiceInteractorIdentity;
@@ -474,8 +477,8 @@ abstract class HotwordDetectorSession {
callback.onError();
return;
}
- callback.onDetected(newResult, null /* audioFormat */,
- null /* audioStream */);
+ callback.onDetected(newResult, /* audioFormat= */ null,
+ /* audioStream= */ null);
Slog.i(TAG, "Egressed "
+ HotwordDetectedResult.getUsageSize(newResult)
+ " bits from hotword trusted process");
@@ -542,6 +545,30 @@ abstract class HotwordDetectorSession {
*/
abstract void informRestartProcessLocked();
+ boolean isSameCallback(@Nullable IHotwordRecognitionStatusCallback callback) {
+ synchronized (mLock) {
+ if (callback == null) {
+ return false;
+ }
+ return mCallback.asBinder().equals(callback.asBinder());
+ }
+ }
+
+ boolean isSameToken(@NonNull IBinder token) {
+ synchronized (mLock) {
+ if (token == null) {
+ return false;
+ }
+ return mToken == token;
+ }
+ }
+
+ boolean isDestroyed() {
+ synchronized (mLock) {
+ return mDestroyed;
+ }
+ }
+
private static Pair<ParcelFileDescriptor, ParcelFileDescriptor> createPipe() {
ParcelFileDescriptor[] fileDescriptors;
try {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java
index 00aec71309c8..4eb997a610a4 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java
@@ -27,6 +27,7 @@ import android.annotation.NonNull;
import android.content.Context;
import android.media.AudioFormat;
import android.media.permission.Identity;
+import android.os.IBinder;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.SharedMemory;
@@ -35,8 +36,8 @@ import android.service.voice.HotwordDetectionService;
import android.service.voice.HotwordDetector;
import android.service.voice.HotwordRejectedResult;
import android.service.voice.IDspHotwordDetectionCallback;
-import android.service.voice.IHotwordDetectionService;
import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
+import android.service.voice.ISandboxedDetectionService;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -63,12 +64,13 @@ final class SoftwareTrustedHotwordDetectorSession extends HotwordDetectorSession
SoftwareTrustedHotwordDetectorSession(
@NonNull HotwordDetectionConnection.ServiceConnection remoteHotwordDetectionService,
- @NonNull Object lock, @NonNull Context context,
+ @NonNull Object lock, @NonNull Context context, @NonNull IBinder token,
@NonNull IHotwordRecognitionStatusCallback callback, int voiceInteractionServiceUid,
Identity voiceInteractorIdentity,
@NonNull ScheduledExecutorService scheduledExecutorService, boolean logging) {
- super(remoteHotwordDetectionService, lock, context, callback, voiceInteractionServiceUid,
- voiceInteractorIdentity, scheduledExecutorService, logging);
+ super(remoteHotwordDetectionService, lock, context, token, callback,
+ voiceInteractionServiceUid, voiceInteractorIdentity, scheduledExecutorService,
+ logging);
}
@SuppressWarnings("GuardedBy")
@@ -167,9 +169,9 @@ final class SoftwareTrustedHotwordDetectorSession extends HotwordDetectorSession
}
@SuppressWarnings("GuardedBy")
- void stopListeningLocked() {
+ void stopListeningFromMicLocked() {
if (DEBUG) {
- Slog.d(TAG, "stopListeningLocked");
+ Slog.d(TAG, "stopListeningFromMicLocked");
}
if (!mPerformingSoftwareHotwordDetection) {
Slog.i(TAG, "Hotword detection is not running");
@@ -177,7 +179,7 @@ final class SoftwareTrustedHotwordDetectorSession extends HotwordDetectorSession
}
mPerformingSoftwareHotwordDetection = false;
- mRemoteHotwordDetectionService.run(IHotwordDetectionService::stopDetection);
+ mRemoteHotwordDetectionService.run(ISandboxedDetectionService::stopDetection);
closeExternalAudioStreamLocked("stopping requested");
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 7207e3738d77..9a0218845038 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -1245,14 +1245,15 @@ public class VoiceInteractionManagerService extends SystemService {
@Override
public void updateState(
@Nullable PersistableBundle options,
- @Nullable SharedMemory sharedMemory) {
+ @Nullable SharedMemory sharedMemory,
+ @NonNull IBinder token) {
super.updateState_enforcePermission();
synchronized (this) {
enforceIsCurrentVoiceInteractionService();
Binder.withCleanCallingIdentity(
- () -> mImpl.updateStateLocked(options, sharedMemory));
+ () -> mImpl.updateStateLocked(options, sharedMemory, token));
}
}
@@ -1262,6 +1263,7 @@ public class VoiceInteractionManagerService extends SystemService {
@NonNull Identity voiceInteractorIdentity,
@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory,
+ @NonNull IBinder token,
IHotwordRecognitionStatusCallback callback,
int detectorType) {
super.initAndVerifyDetector_enforcePermission();
@@ -1274,7 +1276,20 @@ public class VoiceInteractionManagerService extends SystemService {
Binder.withCleanCallingIdentity(
() -> mImpl.initAndVerifyDetectorLocked(voiceInteractorIdentity, options,
- sharedMemory, callback, detectorType));
+ sharedMemory, token, callback, detectorType));
+ }
+ }
+
+ @Override
+ public void destroyDetector(@NonNull IBinder token) {
+ synchronized (this) {
+ if (mImpl == null) {
+ Slog.w(TAG, "destroyDetector without running voice interaction service");
+ return;
+ }
+
+ Binder.withCleanCallingIdentity(
+ () -> mImpl.destroyDetectorLocked(token));
}
}
@@ -1326,6 +1341,7 @@ public class VoiceInteractionManagerService extends SystemService {
ParcelFileDescriptor audioStream,
AudioFormat audioFormat,
PersistableBundle options,
+ @NonNull IBinder token,
IMicrophoneHotwordDetectionVoiceInteractionCallback callback)
throws RemoteException {
synchronized (this) {
@@ -1338,8 +1354,8 @@ public class VoiceInteractionManagerService extends SystemService {
}
final long caller = Binder.clearCallingIdentity();
try {
- mImpl.startListeningFromExternalSourceLocked(
- audioStream, audioFormat, options, callback);
+ mImpl.startListeningFromExternalSourceLocked(audioStream, audioFormat, options,
+ token, callback);
} finally {
Binder.restoreCallingIdentity(caller);
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 6674520091c1..f041adcc7d77 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -56,7 +56,6 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SharedMemory;
import android.os.UserHandle;
-import android.service.voice.HotwordDetector;
import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
import android.service.voice.IVoiceInteractionService;
import android.service.voice.IVoiceInteractionSession;
@@ -113,7 +112,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
VoiceInteractionSessionConnection mActiveSession;
int mDisabledShowContext;
- int mDetectorType;
final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
@@ -552,7 +550,8 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
public void updateStateLocked(
@Nullable PersistableBundle options,
- @Nullable SharedMemory sharedMemory) {
+ @Nullable SharedMemory sharedMemory,
+ @NonNull IBinder token) {
Slog.v(TAG, "updateStateLocked");
if (sharedMemory != null && !sharedMemory.setProtect(OsConstants.PROT_READ)) {
@@ -565,7 +564,7 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
throw new IllegalStateException("Hotword detection connection not found");
}
synchronized (mHotwordDetectionConnection.mLock) {
- mHotwordDetectionConnection.updateStateLocked(options, sharedMemory);
+ mHotwordDetectionConnection.updateStateLocked(options, sharedMemory, token);
}
}
@@ -573,6 +572,7 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
@NonNull Identity voiceInteractorIdentity,
@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory,
+ @NonNull IBinder token,
IHotwordRecognitionStatusCallback callback,
int detectorType) {
Slog.v(TAG, "initAndVerifyDetectorLocked");
@@ -624,16 +624,26 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
throw new IllegalStateException("Can't set sharedMemory to be read-only");
}
- mDetectorType = detectorType;
-
logDetectorCreateEventIfNeeded(callback, detectorType, true,
voiceInteractionServiceUid);
if (mHotwordDetectionConnection == null) {
mHotwordDetectionConnection = new HotwordDetectionConnection(mServiceStub, mContext,
mInfo.getServiceInfo().applicationInfo.uid, voiceInteractorIdentity,
mHotwordDetectionComponentName, mUser, /* bindInstantServiceAllowed= */ false,
- options, sharedMemory, callback, detectorType);
+ detectorType);
}
+ mHotwordDetectionConnection.createDetectorLocked(options, sharedMemory, token, callback,
+ detectorType);
+ }
+
+ public void destroyDetectorLocked(IBinder token) {
+ Slog.v(TAG, "destroyDetectorLocked");
+
+ if (mHotwordDetectionConnection == null) {
+ Slog.w(TAG, "destroy detector callback, but no hotword detection connection");
+ return;
+ }
+ mHotwordDetectionConnection.destroyDetectorLocked(token);
}
private void logDetectorCreateEventIfNeeded(IHotwordRecognitionStatusCallback callback,
@@ -642,19 +652,16 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
HotwordMetricsLogger.writeDetectorCreateEvent(detectorType, isCreated,
voiceInteractionServiceUid);
}
-
}
public void shutdownHotwordDetectionServiceLocked() {
if (DEBUG) {
Slog.d(TAG, "shutdownHotwordDetectionServiceLocked");
}
-
if (mHotwordDetectionConnection == null) {
Slog.w(TAG, "shutdown, but no hotword detection connection");
return;
}
-
mHotwordDetectionConnection.cancelLocked();
mHotwordDetectionConnection = null;
}
@@ -663,7 +670,7 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
AudioFormat audioFormat,
IMicrophoneHotwordDetectionVoiceInteractionCallback callback) {
if (DEBUG) {
- Slog.d(TAG, "startListeningFromMic");
+ Slog.d(TAG, "startListeningFromMicLocked");
}
if (mHotwordDetectionConnection == null) {
@@ -671,16 +678,17 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
return;
}
- mHotwordDetectionConnection.startListeningFromMic(audioFormat, callback);
+ mHotwordDetectionConnection.startListeningFromMicLocked(audioFormat, callback);
}
public void startListeningFromExternalSourceLocked(
ParcelFileDescriptor audioStream,
AudioFormat audioFormat,
@Nullable PersistableBundle options,
+ @NonNull IBinder token,
IMicrophoneHotwordDetectionVoiceInteractionCallback callback) {
if (DEBUG) {
- Slog.d(TAG, "startListeningFromExternalSource");
+ Slog.d(TAG, "startListeningFromExternalSourceLocked");
}
if (mHotwordDetectionConnection == null) {
@@ -693,21 +701,21 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
throw new IllegalStateException("External source is null for hotword detector");
}
- mHotwordDetectionConnection
- .startListeningFromExternalSource(audioStream, audioFormat, options, callback);
+ mHotwordDetectionConnection.startListeningFromExternalSourceLocked(audioStream, audioFormat,
+ options, token, callback);
}
public void stopListeningFromMicLocked() {
if (DEBUG) {
- Slog.d(TAG, "stopListeningFromMic");
+ Slog.d(TAG, "stopListeningFromMicLocked");
}
if (mHotwordDetectionConnection == null) {
- Slog.w(TAG, "stopListeningFromMic() called but connection isn't established");
+ Slog.w(TAG, "stopListeningFromMicLocked() called but connection isn't established");
return;
}
- mHotwordDetectionConnection.stopListening();
+ mHotwordDetectionConnection.stopListeningFromMicLocked();
}
public void triggerHardwareRecognitionEventForTestLocked(
@@ -809,8 +817,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
pw.println(Integer.toHexString(mDisabledShowContext));
}
pw.print(" mBound="); pw.print(mBound); pw.print(" mService="); pw.println(mService);
- pw.print(" mDetectorType=");
- pw.println(HotwordDetector.detectorTypeToString(mDetectorType));
if (mHotwordDetectionConnection != null) {
pw.println(" Hotword detection connection:");
mHotwordDetectionConnection.dump(" ", pw);
@@ -899,5 +905,8 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
@Override
public void onSessionHidden(VoiceInteractionSessionConnection connection) {
mServiceStub.onSessionHidden();
+ // Notifies visibility change here can cause duplicate events, it is added to make sure
+ // client always get the callback even if session is unexpectedly closed.
+ mServiceStub.setSessionWindowVisible(connection.mToken, false);
}
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 8cd5a85011b9..c4744ef21ead 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9987,7 +9987,8 @@ public class CarrierConfigManager {
* Gets the configuration values of the specified keys for a particular subscription.
*
* <p>If an invalid subId is used, the returned configuration will contain default values for
- * the specified keys.
+ * the specified keys. If the value for the key can't be found, the returned configuration will
+ * filter the key out.
*
* <p>After using this method to get the configuration bundle,
* {@link #isConfigForIdentifiedCarrier(PersistableBundle)} should be called to confirm whether
@@ -10005,8 +10006,8 @@ public class CarrierConfigManager {
* @param subId The subscription ID on which the carrier config should be retrieved.
* @param keys The carrier config keys to retrieve values.
* @return A {@link PersistableBundle} with key/value mapping for the specified configuration
- * on success, or an empty (but never null) bundle on failure (for example, when no value for
- * the specified key can be found).
+ * on success, or an empty (but never null) bundle on failure (for example, when the calling app
+ * has no permission).
*/
@RequiresPermission(anyOf = {
Manifest.permission.READ_PHONE_STATE,
@@ -10124,6 +10125,8 @@ public class CarrierConfigManager {
* Gets the configuration values of the specified config keys applied for the default
* subscription.
*
+ * <p>If the value for the key can't be found, the returned bundle will filter the key out.
+ *
* <p>After using this method to get the configuration bundle, {@link
* #isConfigForIdentifiedCarrier(PersistableBundle)} should be called to confirm whether any
* carrier specific configuration has been applied.
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 9b566fbeb0bb..8e8755d9434e 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -58,6 +58,7 @@ import android.os.UserHandle;
import android.provider.Telephony.SimInfo;
import android.telephony.euicc.EuiccManager;
import android.telephony.ims.ImsMmTelManager;
+import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
import android.util.Pair;
@@ -370,7 +371,7 @@ public class SubscriptionManager {
/**
* A content {@link Uri} used to receive updates on advanced calling user setting
- * @see ImsMmTelManager#isAdvancedCallingSettingEnabled().
+ *
* <p>
* Use this {@link Uri} with a {@link ContentObserver} to be notified of changes to the
* subscription advanced calling enabled
@@ -381,6 +382,9 @@ public class SubscriptionManager {
* delivery of updates to the {@link Uri}.
* To be notified of changes to a specific subId, append subId to the URI
* {@link Uri#withAppendedPath(Uri, String)}.
+ *
+ * @see ImsMmTelManager#isAdvancedCallingSettingEnabled()
+ *
* @hide
*/
@NonNull
@@ -1165,7 +1169,7 @@ public class SubscriptionManager {
*
* An opportunistic subscription will default to data-centric.
*
- * {@see SubscriptionInfo#isOpportunistic}
+ * @see SubscriptionInfo#isOpportunistic
*/
public static final int USAGE_SETTING_DEFAULT = 0;
@@ -1949,7 +1953,7 @@ public class SubscriptionManager {
*
* <p>Requires the {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission.
*
- * @see {@link TelephonyManager#getCardIdForDefaultEuicc()} for more information on the card ID.
+ * @see TelephonyManager#getCardIdForDefaultEuicc() for more information on the card ID.
*
* @hide
*/
@@ -1979,7 +1983,7 @@ public class SubscriptionManager {
*
* @param cardId the card ID of the eUICC.
*
- * @see {@link TelephonyManager#getCardIdForDefaultEuicc()} for more information on the card ID.
+ * @see TelephonyManager#getCardIdForDefaultEuicc() for more information on the card ID.
*
* @hide
*/
@@ -2103,10 +2107,15 @@ public class SubscriptionManager {
}
/**
- * Remove SubscriptionInfo record from the SubscriptionInfo database
+ * Remove subscription info record from the subscription database.
+ *
* @param uniqueId This is the unique identifier for the subscription within the specific
- * subscription type.
- * @param subscriptionType the {@link #SUBSCRIPTION_TYPE}
+ * subscription type.
+ * @param subscriptionType the type of subscription to be removed.
+ *
+ * @throws NullPointerException if {@code uniqueId} is {@code null}.
+ * @throws SecurityException if callers do not hold the required permission.
+ *
* @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@@ -2457,20 +2466,6 @@ public class SubscriptionManager {
return getActiveSubscriptionInfo(getDefaultDataSubscriptionId());
}
- /** @hide */
- public void clearSubscriptionInfo() {
- try {
- ISub iSub = TelephonyManager.getSubscriptionService();
- if (iSub != null) {
- iSub.clearSubInfo();
- }
- } catch (RemoteException ex) {
- // ignore it
- }
-
- return;
- }
-
/**
* Check if the supplied subscription ID is valid.
*
@@ -2614,17 +2609,27 @@ public class SubscriptionManager {
}
/**
- * Store properties associated with SubscriptionInfo in database
- * @param subId Subscription Id of Subscription
- * @param propKey Column name in database associated with SubscriptionInfo
- * @param propValue Value to store in DB for particular subId & column name
+ * Set a field in the subscription database. Note not all fields are supported.
+ *
+ * @param subscriptionId Subscription Id of Subscription.
+ * @param columnName Column name in the database. Note not all fields are supported.
+ * @param value Value to store in the database.
+ *
+ * @throws IllegalArgumentException if {@code subscriptionId} is invalid, or the field is not
+ * exposed.
+ * @throws SecurityException if callers do not hold the required permission.
+ *
+ * @see android.provider.Telephony.SimInfo for all the columns.
+ *
* @hide
*/
- public static void setSubscriptionProperty(int subId, String propKey, String propValue) {
+ @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+ public static void setSubscriptionProperty(int subscriptionId, @NonNull String columnName,
+ @NonNull String value) {
try {
ISub iSub = TelephonyManager.getSubscriptionService();
if (iSub != null) {
- iSub.setSubscriptionProperty(subId, propKey, propValue);
+ iSub.setSubscriptionProperty(subscriptionId, columnName, value);
}
} catch (RemoteException ex) {
// ignore it
@@ -2653,118 +2658,149 @@ public class SubscriptionManager {
}
/**
- * Return list of contacts uri corresponding to query result.
- * @param subId Subscription Id of Subscription
- * @param propKey Column name in SubscriptionInfo database
- * @return list of contacts uri to be returned
- * @hide
- */
- private static List<Uri> getContactsFromSubscriptionProperty(int subId, String propKey,
- Context context) {
- String result = getSubscriptionProperty(subId, propKey, context);
- if (result != null) {
- try {
- byte[] b = Base64.decode(result, Base64.DEFAULT);
- ByteArrayInputStream bis = new ByteArrayInputStream(b);
- ObjectInputStream ois = new ObjectInputStream(bis);
- List<String> contacts = ArrayList.class.cast(ois.readObject());
- List<Uri> uris = new ArrayList<>();
- for (String contact : contacts) {
- uris.add(Uri.parse(contact));
- }
- return uris;
- } catch (IOException e) {
- logd("getContactsFromSubscriptionProperty IO exception");
- } catch (ClassNotFoundException e) {
- logd("getContactsFromSubscriptionProperty ClassNotFound exception");
- }
- }
- return new ArrayList<>();
- }
-
- /**
- * Store properties associated with SubscriptionInfo in database
- * @param subId Subscription Id of Subscription
- * @param propKey Column name in SubscriptionInfo database
- * @return Value associated with subId and propKey column in database
+ * Get specific field in string format from the subscription info database.
+ *
+ * @param context The calling context.
+ * @param subscriptionId Subscription id of the subscription.
+ * @param columnName Column name in subscription database.
+ *
+ * @return Value in string format associated with {@code subscriptionId} and {@code columnName}
+ * from the database.
+ *
+ * @throws IllegalArgumentException if {@code subscriptionId} is invalid, or the field is not
+ * exposed.
+ *
+ * @see android.provider.Telephony.SimInfo for all the columns.
+ *
* @hide
*/
- private static String getSubscriptionProperty(int subId, String propKey,
- Context context) {
+ @NonNull
+ @RequiresPermission(anyOf = {
+ Manifest.permission.READ_PHONE_STATE,
+ Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+ "carrier privileges",
+ })
+ private static String getStringSubscriptionProperty(@NonNull Context context,
+ int subscriptionId, @NonNull String columnName) {
String resultValue = null;
try {
ISub iSub = TelephonyManager.getSubscriptionService();
if (iSub != null) {
- resultValue = iSub.getSubscriptionProperty(subId, propKey,
+ resultValue = iSub.getSubscriptionProperty(subscriptionId, columnName,
context.getOpPackageName(), context.getAttributionTag());
}
} catch (RemoteException ex) {
// ignore it
}
- return resultValue;
+ return TextUtils.emptyIfNull(resultValue);
}
/**
- * Returns boolean value corresponding to query result.
- * @param subId Subscription Id of Subscription
- * @param propKey Column name in SubscriptionInfo database
- * @param defValue Default boolean value to be returned
- * @return boolean result value to be returned
+ * Get specific field in {@code boolean} format from the subscription info database.
+ *
+ * @param subscriptionId Subscription id of the subscription.
+ * @param columnName Column name in subscription database.
+ * @param defaultValue Default value in case not found or error.
+ * @param context The calling context.
+ *
+ * @return Value in {@code boolean} format associated with {@code subscriptionId} and
+ * {@code columnName} from the database, or {@code defaultValue} if not found or error.
+ *
+ * @throws IllegalArgumentException if {@code subscriptionId} is invalid, or the field is not
+ * exposed.
+ *
+ * @see android.provider.Telephony.SimInfo for all the columns.
+ *
* @hide
*/
- public static boolean getBooleanSubscriptionProperty(int subId, String propKey,
- boolean defValue, Context context) {
- String result = getSubscriptionProperty(subId, propKey, context);
- if (result != null) {
+ @RequiresPermission(anyOf = {
+ Manifest.permission.READ_PHONE_STATE,
+ Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+ "carrier privileges",
+ })
+ public static boolean getBooleanSubscriptionProperty(int subscriptionId,
+ @NonNull String columnName, boolean defaultValue, @NonNull Context context) {
+ String result = getStringSubscriptionProperty(context, subscriptionId, columnName);
+ if (!result.isEmpty()) {
try {
return Integer.parseInt(result) == 1;
} catch (NumberFormatException err) {
logd("getBooleanSubscriptionProperty NumberFormat exception");
}
}
- return defValue;
+ return defaultValue;
}
/**
- * Returns integer value corresponding to query result.
- * @param subId Subscription Id of Subscription
- * @param propKey Column name in SubscriptionInfo database
- * @param defValue Default integer value to be returned
- * @return integer result value to be returned
+ * Get specific field in {@code integer} format from the subscription info database.
+ *
+ * @param subscriptionId Subscription id of the subscription.
+ * @param columnName Column name in subscription database.
+ * @param defaultValue Default value in case not found or error.
+ * @param context The calling context.
+ *
+ * @return Value in {@code integer} format associated with {@code subscriptionId} and
+ * {@code columnName} from the database, or {@code defaultValue} if not found or error.
+ *
+ * @throws IllegalArgumentException if {@code subscriptionId} is invalid, or the field is not
+ * exposed.
+ *
+ * @see android.provider.Telephony.SimInfo for all the columns.
+ *
* @hide
*/
- public static int getIntegerSubscriptionProperty(int subId, String propKey, int defValue,
- Context context) {
- String result = getSubscriptionProperty(subId, propKey, context);
- if (result != null) {
+ @RequiresPermission(anyOf = {
+ Manifest.permission.READ_PHONE_STATE,
+ Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+ "carrier privileges",
+ })
+ public static int getIntegerSubscriptionProperty(int subscriptionId, @NonNull String columnName,
+ int defaultValue, @NonNull Context context) {
+ String result = getStringSubscriptionProperty(context, subscriptionId, columnName);
+ if (!result.isEmpty()) {
try {
return Integer.parseInt(result);
} catch (NumberFormatException err) {
logd("getIntegerSubscriptionProperty NumberFormat exception");
}
}
- return defValue;
+ return defaultValue;
}
/**
- * Returns long value corresponding to query result.
- * @param subId Subscription Id of Subscription
- * @param propKey Column name in SubscriptionInfo database
- * @param defValue Default long value to be returned
- * @return long result value to be returned
+ * Get specific field in {@code long} format from the subscription info database.
+ *
+ * @param subscriptionId Subscription id of the subscription.
+ * @param columnName Column name in subscription database.
+ * @param defaultValue Default value in case not found or error.
+ * @param context The calling context.
+ *
+ * @return Value in {@code long} format associated with {@code subscriptionId} and
+ * {@code columnName} from the database, or {@code defaultValue} if not found or error.
+ *
+ * @throws IllegalArgumentException if {@code subscriptionId} is invalid, or the field is not
+ * exposed.
+ *
+ * @see android.provider.Telephony.SimInfo for all the columns.
+ *
* @hide
*/
- public static long getLongSubscriptionProperty(int subId, String propKey, long defValue,
- Context context) {
- String result = getSubscriptionProperty(subId, propKey, context);
- if (result != null) {
+ @RequiresPermission(anyOf = {
+ Manifest.permission.READ_PHONE_STATE,
+ Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+ "carrier privileges",
+ })
+ public static long getLongSubscriptionProperty(int subscriptionId, @NonNull String columnName,
+ long defaultValue, @NonNull Context context) {
+ String result = getStringSubscriptionProperty(context, subscriptionId, columnName);
+ if (!result.isEmpty()) {
try {
return Long.parseLong(result);
} catch (NumberFormatException err) {
logd("getLongSubscriptionProperty NumberFormat exception");
}
}
- return defValue;
+ return defaultValue;
}
/**
@@ -3002,7 +3038,6 @@ public class SubscriptionManager {
* considered unmetered.
* @param networkTypes the network types this override applies to. If no
* network types are specified, override values will be ignored.
- * {@see TelephonyManager#getAllNetworkTypes()}
* @param expirationDurationMillis the duration after which the requested override
* will be automatically cleared, or {@code 0} to leave in the
* requested state until explicitly cleared, or the next reboot,
@@ -3063,17 +3098,14 @@ public class SubscriptionManager {
* </ul>
*
* @param subId the subscriber this override applies to.
- * @param overrideCongested set if the subscription should be considered
- * congested.
- * @param networkTypes the network types this override applies to. If no
- * network types are specified, override values will be ignored.
- * {@see TelephonyManager#getAllNetworkTypes()}
+ * @param overrideCongested set if the subscription should be considered congested.
+ * @param networkTypes the network types this override applies to. If no network types are
+ * specified, override values will be ignored.
* @param expirationDurationMillis the duration after which the requested override
- * will be automatically cleared, or {@code 0} to leave in the
- * requested state until explicitly cleared, or the next reboot,
- * whichever happens first.
- * @throws SecurityException if the caller doesn't meet the requirements
- * outlined above.
+ * will be automatically cleared, or {@code 0} to leave in the requested state until explicitly
+ * cleared, or the next reboot, whichever happens first.
+ *
+ * @throws SecurityException if the caller doesn't meet the requirements outlined above.
*/
public void setSubscriptionOverrideCongested(int subId, boolean overrideCongested,
@NonNull @Annotation.NetworkType int[] networkTypes,
@@ -3089,10 +3121,11 @@ public class SubscriptionManager {
*
* Only supported for embedded subscriptions (if {@link SubscriptionInfo#isEmbedded} returns
* true). To check for permissions for non-embedded subscription as well,
- * {@see android.telephony.TelephonyManager#hasCarrierPrivileges}.
*
* @param info The subscription to check.
* @return whether the app is authorized to manage this subscription per its metadata.
+ *
+ * @see android.telephony.TelephonyManager#hasCarrierPrivileges
*/
public boolean canManageSubscription(SubscriptionInfo info) {
return canManageSubscription(info, mContext.getPackageName());
@@ -3105,11 +3138,13 @@ public class SubscriptionManager {
*
* Only supported for embedded subscriptions (if {@link SubscriptionInfo#isEmbedded} returns
* true). To check for permissions for non-embedded subscription as well,
- * {@see android.telephony.TelephonyManager#hasCarrierPrivileges}.
*
* @param info The subscription to check.
* @param packageName Package name of the app to check.
+ *
* @return whether the app is authorized to manage this subscription per its access rules.
+ *
+ * @see android.telephony.TelephonyManager#hasCarrierPrivileges
* @hide
*/
@SystemApi
@@ -3423,21 +3458,20 @@ public class SubscriptionManager {
/**
* Remove a list of subscriptions from their subscription group.
- * See {@link #createSubscriptionGroup(List)} for more details.
*
* Caller will either have {@link android.Manifest.permission#MODIFY_PHONE_STATE}
- * permission or had carrier privilege permission on the subscriptions:
- * {@link TelephonyManager#hasCarrierPrivileges()} or
- * {@link #canManageSubscription(SubscriptionInfo)}
- *
- * @throws SecurityException if the caller doesn't meet the requirements
- * outlined above.
- * @throws IllegalArgumentException if the some subscriptions in the list doesn't belong
- * the specified group.
- * @throws IllegalStateException if Telephony service is in bad state.
+ * permission or has carrier privilege permission on all of the subscriptions provided in
+ * {@code subIdList}.
*
* @param subIdList list of subId that need removing from their groups.
+ * @param groupUuid The UUID of the subscription group.
+ *
+ * @throws SecurityException if the caller doesn't meet the requirements outlined above.
+ * @throws IllegalArgumentException if the some subscriptions in the list doesn't belong the
+ * specified group.
+ * @throws IllegalStateException if Telephony service is in bad state.
*
+ * @see #createSubscriptionGroup(List)
*/
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@@ -3445,7 +3479,7 @@ public class SubscriptionManager {
@NonNull ParcelUuid groupUuid) {
Preconditions.checkNotNull(subIdList, "subIdList can't be null.");
Preconditions.checkNotNull(groupUuid, "groupUuid can't be null.");
- String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>";
+ String callingPackage = mContext != null ? mContext.getOpPackageName() : "<unknown>";
if (VDBG) {
logd("[removeSubscriptionsFromGroup]");
}
@@ -3455,7 +3489,7 @@ public class SubscriptionManager {
try {
ISub iSub = TelephonyManager.getSubscriptionService();
if (iSub != null) {
- iSub.removeSubscriptionsFromGroup(subIdArray, groupUuid, pkgForDebug);
+ iSub.removeSubscriptionsFromGroup(subIdArray, groupUuid, callingPackage);
} else {
if (!isSystemProcess()) {
throw new IllegalStateException("telephony service is null.");
@@ -3493,7 +3527,6 @@ public class SubscriptionManager {
* @param groupUuid of which list of subInfo will be returned.
* @return list of subscriptionInfo that belong to the same group, including the given
* subscription itself. It will return an empty list if no subscription belongs to the group.
- *
*/
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(Manifest.permission.READ_PHONE_STATE)
@@ -3533,7 +3566,8 @@ public class SubscriptionManager {
* want to see their own hidden subscriptions.
*
* @param info the subscriptionInfo to check against.
- * @return true if this subscription should be visible to the API caller.
+ *
+ * @return {@code true} if this subscription should be visible to the API caller.
*
* @hide
*/
@@ -3606,9 +3640,9 @@ public class SubscriptionManager {
* <p>
* Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required
*
+ * @param subscriptionId Subscription to be enabled or disabled. It could be a eSIM or pSIM
+ * subscription.
* @param enable whether user is turning it on or off.
- * @param subscriptionId Subscription to be enabled or disabled.
- * It could be a eSIM or pSIM subscription.
*
* @return whether the operation is successful.
*
@@ -3641,8 +3675,6 @@ public class SubscriptionManager {
* available from SubscriptionInfo.areUiccApplicationsEnabled() will be updated
* immediately.)
*
- * Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required
- *
* @param subscriptionId which subscription to operate on.
* @param enabled whether uicc applications are enabled or disabled.
* @hide
@@ -3675,8 +3707,6 @@ public class SubscriptionManager {
* It provides whether a physical SIM card can be disabled without taking it out, which is done
* via {@link #setSubscriptionEnabled(int, boolean)} API.
*
- * Requires Permission: READ_PRIVILEGED_PHONE_STATE.
- *
* @return whether can disable subscriptions on physical SIMs.
*
* @hide
@@ -3704,13 +3734,9 @@ public class SubscriptionManager {
}
/**
- * Check if a subscription is active.
- *
- * @param subscriptionId The subscription id to check.
- *
- * @return {@code true} if the subscription is active.
+ * Check if the subscription is currently active in any slot.
*
- * @throws IllegalArgumentException if the provided slot index is invalid.
+ * @param subscriptionId The subscription id.
*
* @hide
*/
@@ -3730,15 +3756,14 @@ public class SubscriptionManager {
}
/**
- * Set the device to device status sharing user preference for a subscription ID. The setting
+ * Set the device to device status sharing user preference for a subscription id. The setting
* app uses this method to indicate with whom they wish to share device to device status
* information.
*
- * @param subscriptionId the unique Subscription ID in database.
- * @param sharing the status sharing preference.
+ * @param subscriptionId The subscription id.
+ * @param sharing The status sharing preference.
*
- * @throws IllegalArgumentException if the subscription does not exist, or the sharing
- * preference is invalid.
+ * @throws SecurityException if the caller doesn't have permissions required.
*/
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
public void setDeviceToDeviceStatusSharingPreference(int subscriptionId,
@@ -3755,6 +3780,8 @@ public class SubscriptionManager {
* Returns the user-chosen device to device status sharing preference
* @param subscriptionId Subscription id of subscription
* @return The device to device status sharing preference
+ *
+ * @throws SecurityException if the caller doesn't have permissions required.
*/
public @DeviceToDeviceStatusSharingPreference int getDeviceToDeviceStatusSharingPreference(
int subscriptionId) {
@@ -3766,15 +3793,14 @@ public class SubscriptionManager {
}
/**
- * Set the list of contacts that allow device to device status sharing for a subscription ID.
+ * Set the list of contacts that allow device to device status sharing for a subscription id.
* The setting app uses this method to indicate with whom they wish to share device to device
* status information.
*
- * @param subscriptionId The unique Subscription ID in database.
+ * @param subscriptionId The subscription id.
* @param contacts The list of contacts that allow device to device status sharing.
*
- * @throws IllegalArgumentException if the subscription does not exist, or contacts is
- * {@code null}.
+ * @throws SecurityException if the caller doesn't have permissions required.
*/
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
public void setDeviceToDeviceStatusSharingContacts(int subscriptionId,
@@ -3790,38 +3816,46 @@ public class SubscriptionManager {
}
/**
- * Returns the list of contacts that allow device to device status sharing.
- * @param subscriptionId Subscription id of subscription
- * @return The list of contacts that allow device to device status sharing
+ * Get the list of contacts that allow device to device status sharing.
+ *
+ * @param subscriptionId Subscription id.
+ *
+ * @return The list of contacts that allow device to device status sharing.
*/
- public @NonNull List<Uri> getDeviceToDeviceStatusSharingContacts(
- int subscriptionId) {
- if (VDBG) {
- logd("[getDeviceToDeviceStatusSharingContacts] + subId: " + subscriptionId);
+ public @NonNull List<Uri> getDeviceToDeviceStatusSharingContacts(int subscriptionId) {
+ String result = getStringSubscriptionProperty(mContext, subscriptionId,
+ D2D_STATUS_SHARING_SELECTED_CONTACTS);
+ if (result != null) {
+ try {
+ byte[] b = Base64.decode(result, Base64.DEFAULT);
+ ByteArrayInputStream bis = new ByteArrayInputStream(b);
+ ObjectInputStream ois = new ObjectInputStream(bis);
+ List<String> contacts = ArrayList.class.cast(ois.readObject());
+ List<Uri> uris = new ArrayList<>();
+ for (String contact : contacts) {
+ uris.add(Uri.parse(contact));
+ }
+ return uris;
+ } catch (IOException e) {
+ logd("getDeviceToDeviceStatusSharingContacts IO exception");
+ } catch (ClassNotFoundException e) {
+ logd("getDeviceToDeviceStatusSharingContacts ClassNotFound exception");
+ }
}
- return getContactsFromSubscriptionProperty(subscriptionId,
- D2D_STATUS_SHARING_SELECTED_CONTACTS, mContext);
+ return new ArrayList<>();
}
/**
- * Get the active subscription id by logical SIM slot index.
- *
- * @param slotIndex The logical SIM slot index.
- * @return The active subscription id.
- *
- * @throws IllegalArgumentException if the provided slot index is invalid.
- *
+ * DO NOT USE.
+ * This API is designed for features that are not finished at this point. Do not call this API.
* @hide
+ * TODO b/135547512: further clean up
*/
@SystemApi
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public int getEnabledSubscriptionId(int slotIndex) {
int subId = INVALID_SUBSCRIPTION_ID;
- if (!isValidSlotIndex(slotIndex)) {
- throw new IllegalArgumentException("Invalid slot index " + slotIndex);
- }
-
try {
ISub iSub = TelephonyManager.getSubscriptionService();
if (iSub != null) {
@@ -3863,7 +3897,7 @@ public class SubscriptionManager {
/**
* Get active data subscription id. Active data subscription refers to the subscription
* currently chosen to provide cellular internet connection to the user. This may be
- * different from getDefaultDataSubscriptionId().
+ * different from {@link #getDefaultDataSubscriptionId()}.
*
* @return Active data subscription id if any is chosen, or {@link #INVALID_SUBSCRIPTION_ID} if
* not.
@@ -4061,12 +4095,15 @@ public class SubscriptionManager {
* security-related or other sensitive scenarios.
*
* @param subscriptionId the subscription ID, or {@link #DEFAULT_SUBSCRIPTION_ID}
- * for the default one.
+ * for the default one.
* @param source the source of the phone number, one of the PHONE_NUMBER_SOURCE_* constants.
+ *
* @return the phone number, or an empty string if not available.
+ *
* @throws IllegalArgumentException if {@code source} is invalid.
* @throws IllegalStateException if the telephony process is not currently available.
* @throws SecurityException if the caller doesn't have permissions required.
+ *
* @see #PHONE_NUMBER_SOURCE_UICC
* @see #PHONE_NUMBER_SOURCE_CARRIER
* @see #PHONE_NUMBER_SOURCE_IMS
@@ -4123,8 +4160,10 @@ public class SubscriptionManager {
* @param subscriptionId the subscription ID, or {@link #DEFAULT_SUBSCRIPTION_ID}
* for the default one.
* @return the phone number, or an empty string if not available.
+ *
* @throws IllegalStateException if the telephony process is not currently available.
* @throws SecurityException if the caller doesn't have permissions required.
+ *
* @see #getPhoneNumber(int, int)
*/
@RequiresPermission(anyOf = {
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index c926a23f55ce..5d49413e93da 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -17808,11 +17808,11 @@ public class TelephonyManager {
* @hide
*/
@RequiresPermission(Manifest.permission.READ_PHONE_STATE)
- public void isNullCipherAndIntegrityPreferenceEnabled() {
+ public boolean isNullCipherAndIntegrityPreferenceEnabled() {
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- telephony.isNullCipherAndIntegrityPreferenceEnabled();
+ return telephony.isNullCipherAndIntegrityPreferenceEnabled();
} else {
throw new IllegalStateException("telephony service is null.");
}
@@ -17820,5 +17820,6 @@ public class TelephonyManager {
Rlog.e(TAG, "isNullCipherAndIntegrityPreferenceEnabled RemoteException", ex);
ex.rethrowFromSystemServer();
}
+ return true;
}
}
diff --git a/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl b/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl
index 640426b45ba3..c8c8724f3f13 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl
@@ -35,4 +35,5 @@ interface IImsMmTelListener {
void onRejectedCall(in ImsCallProfile callProfile, in ImsReasonInfo reason);
oneway void onVoiceMessageCountUpdate(int count);
oneway void onAudioModeIsVoipChanged(int imsAudioHandler);
+ oneway void onTriggerEpsFallback(int reason);
}
diff --git a/telephony/java/android/telephony/ims/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
index 4710c1f4dbf3..1c7e9b9b281f 100644
--- a/telephony/java/android/telephony/ims/feature/MmTelFeature.java
+++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
@@ -590,6 +590,17 @@ public class MmTelFeature extends ImsFeature {
public void onAudioModeIsVoipChanged(int imsAudioHandler) {
}
+
+ /**
+ * Called when the IMS triggers EPS fallback procedure.
+ *
+ * @param reason specifies the reason that causes EPS fallback.
+ * @hide
+ */
+ @Override
+ public void onTriggerEpsFallback(@EpsFallbackReason int reason) {
+
+ }
}
/**
@@ -662,6 +673,48 @@ public class MmTelFeature extends ImsFeature {
@SystemApi
public static final int AUDIO_HANDLER_BASEBAND = 1;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = "EPS_FALLBACK_REASON_",
+ value = {
+ EPS_FALLBACK_REASON_INVALID,
+ EPS_FALLBACK_REASON_NO_NETWORK_TRIGGER,
+ EPS_FALLBACK_REASON_NO_NETWORK_RESPONSE,
+ })
+ public @interface EpsFallbackReason {}
+
+ /**
+ * Default value. Internal use only.
+ * This value should not be used to trigger EPS fallback.
+ * @hide
+ */
+ public static final int EPS_FALLBACK_REASON_INVALID = -1;
+
+ /**
+ * If the network only supports the EPS fallback in 5G NR SA for voice calling and the EPS
+ * Fallback procedure by the network during the call setup is not triggered, UE initiated
+ * fallback will be triggered with this reason. The modem shall locally release the 5G NR
+ * SA RRC connection and acquire the LTE network and perform a tracking area update
+ * procedure. After the EPS fallback procedure is completed, the call setup for voice will
+ * be established if there is no problem.
+ *
+ * @hide
+ */
+ public static final int EPS_FALLBACK_REASON_NO_NETWORK_TRIGGER = 1;
+
+ /**
+ * If the UE doesn't receive any response for SIP INVITE within a certain timeout in 5G NR
+ * SA for MO voice calling, the device determines that voice call is not available in 5G and
+ * terminates all active SIP dialogs and SIP requests and enters IMS non-registered state.
+ * In that case, UE initiated fallback will be triggered with this reason. The modem shall
+ * reset modem's data buffer of IMS PDU to prevent the ghost call. After the EPS fallback
+ * procedure is completed, VoLTE call could be tried if there is no problem.
+ *
+ * @hide
+ */
+ public static final int EPS_FALLBACK_REASON_NO_NETWORK_RESPONSE = 2;
+
private IImsMmTelListener mListener;
/**
@@ -830,6 +883,24 @@ public class MmTelFeature extends ImsFeature {
}
/**
+ * Triggers the EPS fallback procedure.
+ *
+ * @param reason specifies the reason that causes EPS fallback.
+ * @hide
+ */
+ public final void triggerEpsFallback(@EpsFallbackReason int reason) {
+ IImsMmTelListener listener = getListener();
+ if (listener == null) {
+ throw new IllegalStateException("Session is not available.");
+ }
+ try {
+ listener.onTriggerEpsFallback(reason);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
* Provides the MmTelFeature with the ability to return the framework Capability Configuration
* for a provided Capability. If the framework calls {@link #changeEnabledCapabilities} and
* includes a capability A to enable or disable, this method should return the correct enabled
diff --git a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
index 897b57f48dad..ad8a936c3c27 100644
--- a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
@@ -22,6 +22,7 @@ import android.annotation.SystemApi;
import android.content.Context;
import android.os.PersistableBundle;
import android.os.RemoteException;
+import android.telephony.ims.ImsService;
import android.telephony.ims.ProvisioningManager;
import android.telephony.ims.RcsClientConfiguration;
import android.telephony.ims.RcsConfig;
@@ -553,7 +554,7 @@ public class ImsConfigImplBase {
ImsConfigStub mImsConfigStub;
/**
- * Create a ImsConfig using the Executor specified for methods being called by the
+ * Create an ImsConfig using the Executor specified for methods being called by the
* framework.
* @param executor The executor for the framework to use when executing the methods overridden
* by the implementation of ImsConfig.
@@ -569,6 +570,9 @@ public class ImsConfigImplBase {
mImsConfigStub = new ImsConfigStub(this, null);
}
+ /**
+ * Create an ImsConfig using the Executor defined in {@link ImsService#getExecutor}
+ */
public ImsConfigImplBase() {
mImsConfigStub = new ImsConfigStub(this, null);
}
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 280d25950228..c5f6902062ff 100644
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -242,8 +242,6 @@ interface ISub {
int getDefaultSubId();
- int clearSubInfo();
-
int getPhoneId(int subId);
/**
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
index 4e7ab7a24f65..b064695554bb 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
@@ -20,6 +20,7 @@ import android.app.Instrumentation
import android.app.WallpaperManager
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
import com.android.server.wm.flicker.FlickerBuilder
@@ -29,11 +30,15 @@ import com.android.server.wm.flicker.helpers.NewTasksAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.DEFAULT_TASK_DISPLAY_AREA
import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.SPLASH_SCREEN
import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.WALLPAPER_BBQ_WRAPPER
+import com.android.server.wm.traces.common.ComponentSplashScreenMatcher
import com.android.server.wm.traces.common.IComponentMatcher
import com.android.server.wm.traces.parser.toFlickerComponent
+import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -43,7 +48,7 @@ import org.junit.runners.Parameterized
/**
* Test the back and forward transition between 2 activities.
*
- * To run this test: `atest FlickerTests:ActivitiesTransitionTest`
+ * To run this test: `atest FlickerTests:TaskTransitionTest`
*
* Actions:
* ```
@@ -57,7 +62,7 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class TaskTransitionTest(flicker: FlickerTest) : BaseTest(flicker) {
- private val testApp = NewTasksAppHelper(instrumentation)
+ private val launchNewTaskApp = NewTasksAppHelper(instrumentation)
private val simpleApp = SimpleAppHelper(instrumentation)
private val wallpaper by lazy {
getWallpaperPackage(instrumentation) ?: error("Unable to obtain wallpaper")
@@ -65,10 +70,10 @@ class TaskTransitionTest(flicker: FlickerTest) : BaseTest(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
- setup { testApp.launchViaIntent(wmHelper) }
- teardown { testApp.exit(wmHelper) }
+ setup { launchNewTaskApp.launchViaIntent(wmHelper) }
+ teardown { launchNewTaskApp.exit(wmHelper) }
transitions {
- testApp.openNewTask(device, wmHelper)
+ launchNewTaskApp.openNewTask(device, wmHelper)
tapl.pressBack()
wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
}
@@ -101,7 +106,7 @@ class TaskTransitionTest(flicker: FlickerTest) : BaseTest(flicker) {
* Check that the [ComponentNameMatcher.LAUNCHER] window is never visible when performing task
* transitions. A solid color background should be shown above it.
*/
- @Postsubmit
+ @Presubmit
@Test
fun launcherWindowIsNeverVisible() {
flicker.assertWm { this.isAppWindowInvisible(ComponentNameMatcher.LAUNCHER) }
@@ -111,42 +116,76 @@ class TaskTransitionTest(flicker: FlickerTest) : BaseTest(flicker) {
* Checks that the [ComponentNameMatcher.LAUNCHER] layer is never visible when performing task
* transitions. A solid color background should be shown above it.
*/
- @Postsubmit
+ @Presubmit
@Test
fun launcherLayerIsNeverVisible() {
flicker.assertLayers { this.isInvisible(ComponentNameMatcher.LAUNCHER) }
}
/** Checks that a color background is visible while the task transition is occurring. */
- @FlakyTest(bugId = 240570652)
+ @Presubmit
@Test
- fun colorLayerIsVisibleDuringTransition() {
- val bgColorLayer = ComponentNameMatcher("", "colorBackgroundLayer")
- val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
+ fun transitionHasColorBackground_legacy() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ transitionHasColorBackground(DEFAULT_TASK_DISPLAY_AREA)
+ }
+ /** Checks that a color background is visible while the task transition is occurring. */
+ @Presubmit
+ @Test
+ fun transitionHasColorBackground_shellTransit() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ transitionHasColorBackground(ComponentNameMatcher("", "Animation Background"))
+ }
+
+ private fun transitionHasColorBackground(backgroundColorLayer: IComponentMatcher) {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+
+ val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
flicker.assertLayers {
- this.invoke("LAUNCH_NEW_TASK_ACTIVITY coversExactly displayBounds") {
- it.visibleRegion(testApp.componentMatcher).coversExactly(displayBounds)
+ this
+ .invoke("LAUNCH_NEW_TASK_ACTIVITY coversExactly displayBounds") {
+ it.visibleRegion(launchNewTaskApp.componentMatcher).coversExactly(displayBounds)
}
- .isInvisible(bgColorLayer)
+ .isInvisible(backgroundColorLayer)
+ .hasNoColor(backgroundColorLayer)
.then()
// Transitioning
- .isVisible(bgColorLayer)
+ .isVisible(backgroundColorLayer)
+ .hasColor(backgroundColorLayer)
.then()
// Fully transitioned to simple SIMPLE_ACTIVITY
+ .invoke(
+ "SIMPLE_ACTIVITY's splashscreen coversExactly displayBounds",
+ isOptional = true
+ ) {
+ it.visibleRegion(ComponentSplashScreenMatcher( simpleApp.componentMatcher))
+ .coversExactly(displayBounds)
+ }
.invoke("SIMPLE_ACTIVITY coversExactly displayBounds") {
it.visibleRegion(simpleApp.componentMatcher).coversExactly(displayBounds)
}
- .isInvisible(bgColorLayer)
+ .isInvisible(backgroundColorLayer)
+ .hasNoColor(backgroundColorLayer)
.then()
// Transitioning back
- .isVisible(bgColorLayer)
+ .isVisible(backgroundColorLayer)
+ .hasColor(backgroundColorLayer)
.then()
// Fully transitioned back to LAUNCH_NEW_TASK_ACTIVITY
+ .invoke(
+ "LAUNCH_NEW_TASK_ACTIVITY's splashscreen coversExactly displayBounds",
+ isOptional = true
+ ) {
+ it.visibleRegion(
+ ComponentSplashScreenMatcher(launchNewTaskApp.componentMatcher))
+ .coversExactly(displayBounds)
+ }
.invoke("LAUNCH_NEW_TASK_ACTIVITY coversExactly displayBounds") {
- it.visibleRegion(testApp.componentMatcher).coversExactly(displayBounds)
+ it.visibleRegion(launchNewTaskApp.componentMatcher).coversExactly(displayBounds)
}
- .isInvisible(bgColorLayer)
+ .isInvisible(backgroundColorLayer)
+ .hasNoColor(backgroundColorLayer)
}
}
@@ -158,7 +197,7 @@ class TaskTransitionTest(flicker: FlickerTest) : BaseTest(flicker) {
@Test
fun newTaskOpensOnTopAndThenCloses() {
flicker.assertWm {
- this.isAppWindowOnTop(testApp.componentMatcher)
+ this.isAppWindowOnTop(launchNewTaskApp.componentMatcher)
.then()
.isAppWindowOnTop(SPLASH_SCREEN, isOptional = true)
.then()
@@ -166,66 +205,15 @@ class TaskTransitionTest(flicker: FlickerTest) : BaseTest(flicker) {
.then()
.isAppWindowOnTop(SPLASH_SCREEN, isOptional = true)
.then()
- .isAppWindowOnTop(testApp.componentMatcher)
+ .isAppWindowOnTop(launchNewTaskApp.componentMatcher)
}
}
/** {@inheritDoc} */
- @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
-
- /** {@inheritDoc} */
@Postsubmit
@Test
override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
- /** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
-
- /** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun statusBarLayerIsVisibleAtStartAndEnd() =
- super.statusBarLayerIsVisibleAtStartAndEnd()
-
- /** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
-
- /** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
-
- /** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
-
- /** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
-
- /** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
-
- /** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
- /** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
companion object {
private fun getWallpaperPackage(instrumentation: Instrumentation): IComponentMatcher? {
val wallpaperManager = WallpaperManager.getInstance(instrumentation.targetContext)
diff --git a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
index 2aef9ae7ca32..40408880a2c6 100644
--- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
@@ -19,9 +19,11 @@ package android.net.vcn;
import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_MOBIKE;
import static android.net.vcn.VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES;
import static android.net.vcn.VcnGatewayConnectionConfig.UNDERLYING_NETWORK_TEMPLATES_KEY;
+import static android.net.vcn.VcnGatewayConnectionConfig.VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertTrue;
@@ -42,7 +44,9 @@ import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.TimeUnit;
@RunWith(AndroidJUnit4.class)
@@ -79,6 +83,9 @@ public class VcnGatewayConnectionConfigTest {
};
public static final int MAX_MTU = 1360;
+ private static final Set<Integer> GATEWAY_OPTIONS =
+ Collections.singleton(VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY);
+
public static final IkeTunnelConnectionParams TUNNEL_CONNECTION_PARAMS =
TunnelConnectionParamsUtilsTest.buildTestParams();
@@ -109,10 +116,16 @@ public class VcnGatewayConnectionConfigTest {
TUNNEL_CONNECTION_PARAMS);
}
- private static VcnGatewayConnectionConfig buildTestConfigWithExposedCaps(
- VcnGatewayConnectionConfig.Builder builder, int... exposedCaps) {
+ private static VcnGatewayConnectionConfig buildTestConfigWithExposedCapsAndOptions(
+ VcnGatewayConnectionConfig.Builder builder,
+ Set<Integer> gatewayOptions,
+ int... exposedCaps) {
builder.setRetryIntervalsMillis(RETRY_INTERVALS_MS).setMaxMtu(MAX_MTU);
+ for (int option : gatewayOptions) {
+ builder.addGatewayOption(option);
+ }
+
for (int caps : exposedCaps) {
builder.addExposedCapability(caps);
}
@@ -120,11 +133,28 @@ public class VcnGatewayConnectionConfigTest {
return builder.build();
}
+ private static VcnGatewayConnectionConfig buildTestConfigWithExposedCaps(
+ VcnGatewayConnectionConfig.Builder builder, int... exposedCaps) {
+ return buildTestConfigWithExposedCapsAndOptions(
+ builder, Collections.emptySet(), exposedCaps);
+ }
+
// Public for use in VcnGatewayConnectionTest
public static VcnGatewayConnectionConfig buildTestConfigWithExposedCaps(int... exposedCaps) {
return buildTestConfigWithExposedCaps(newBuilder(), exposedCaps);
}
+ private static VcnGatewayConnectionConfig buildTestConfigWithGatewayOptions(
+ VcnGatewayConnectionConfig.Builder builder, Set<Integer> gatewayOptions) {
+ return buildTestConfigWithExposedCapsAndOptions(builder, gatewayOptions, EXPOSED_CAPS);
+ }
+
+ // Public for use in VcnGatewayConnectionTest
+ public static VcnGatewayConnectionConfig buildTestConfigWithGatewayOptions(
+ Set<Integer> gatewayOptions) {
+ return buildTestConfigWithExposedCapsAndOptions(newBuilder(), gatewayOptions, EXPOSED_CAPS);
+ }
+
@Test
public void testBuilderRequiresNonNullGatewayConnectionName() {
try {
@@ -211,6 +241,15 @@ public class VcnGatewayConnectionConfigTest {
}
@Test
+ public void testBuilderRequiresValidOption() {
+ try {
+ newBuilder().addGatewayOption(-1);
+ fail("Expected exception due to the invalid VCN gateway option");
+ } catch (IllegalArgumentException e) {
+ }
+ }
+
+ @Test
public void testBuilderAndGetters() {
final VcnGatewayConnectionConfig config = buildTestConfig();
@@ -225,6 +264,20 @@ public class VcnGatewayConnectionConfigTest {
assertArrayEquals(RETRY_INTERVALS_MS, config.getRetryIntervalsMillis());
assertEquals(MAX_MTU, config.getMaxMtu());
+
+ assertFalse(
+ config.hasGatewayOption(
+ VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY));
+ }
+
+ @Test
+ public void testBuilderAndGettersWithOptions() {
+ final VcnGatewayConnectionConfig config =
+ buildTestConfigWithGatewayOptions(GATEWAY_OPTIONS);
+
+ for (int option : GATEWAY_OPTIONS) {
+ assertTrue(config.hasGatewayOption(option));
+ }
}
@Test
@@ -235,6 +288,14 @@ public class VcnGatewayConnectionConfigTest {
}
@Test
+ public void testPersistableBundleWithOptions() {
+ final VcnGatewayConnectionConfig config =
+ buildTestConfigWithGatewayOptions(GATEWAY_OPTIONS);
+
+ assertEquals(config, new VcnGatewayConnectionConfig(config.toPersistableBundle()));
+ }
+
+ @Test
public void testParsePersistableBundleWithoutVcnUnderlyingNetworkTemplates() {
PersistableBundle configBundle = buildTestConfig().toPersistableBundle();
configBundle.putPersistableBundle(UNDERLYING_NETWORK_TEMPLATES_KEY, null);
@@ -318,4 +379,27 @@ public class VcnGatewayConnectionConfigTest {
assertNotEquals(UNDERLYING_NETWORK_TEMPLATES, networkTemplatesNotEqual);
assertNotEquals(config, configNotEqual);
}
+
+ private static VcnGatewayConnectionConfig buildConfigWithGatewayOptionsForEqualityTest(
+ Set<Integer> gatewayOptions) {
+ return buildTestConfigWithGatewayOptions(
+ new VcnGatewayConnectionConfig.Builder(
+ "buildConfigWithGatewayOptionsForEqualityTest", TUNNEL_CONNECTION_PARAMS),
+ gatewayOptions);
+ }
+
+ @Test
+ public void testVcnGatewayOptionsEquality() throws Exception {
+ final VcnGatewayConnectionConfig config =
+ buildConfigWithGatewayOptionsForEqualityTest(GATEWAY_OPTIONS);
+
+ final VcnGatewayConnectionConfig configEqual =
+ buildConfigWithGatewayOptionsForEqualityTest(GATEWAY_OPTIONS);
+
+ final VcnGatewayConnectionConfig configNotEqual =
+ buildConfigWithGatewayOptionsForEqualityTest(Collections.emptySet());
+
+ assertEquals(config, configEqual);
+ assertNotEquals(config, configNotEqual);
+ }
}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
index 15d4f1097108..1c21a067bde8 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
@@ -50,9 +50,11 @@ import static org.mockito.Mockito.when;
import static java.util.Collections.singletonList;
+import android.net.ConnectivityDiagnosticsManager.DataStallReport;
import android.net.ConnectivityManager;
import android.net.LinkAddress;
import android.net.LinkProperties;
+import android.net.Network;
import android.net.NetworkAgent;
import android.net.NetworkCapabilities;
import android.net.ipsec.ike.ChildSaProposal;
@@ -63,10 +65,12 @@ import android.net.ipsec.ike.exceptions.IkeProtocolException;
import android.net.vcn.VcnGatewayConnectionConfig;
import android.net.vcn.VcnGatewayConnectionConfigTest;
import android.net.vcn.VcnManager.VcnErrorCode;
+import android.os.PersistableBundle;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.vcn.routeselection.UnderlyingNetworkRecord;
import com.android.server.vcn.util.MtuUtils;
import org.junit.Before;
@@ -88,6 +92,7 @@ import java.util.function.Consumer;
public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnectionTestBase {
private VcnIkeSession mIkeSession;
private VcnNetworkAgent mNetworkAgent;
+ private Network mVcnNetwork;
@Before
public void setUp() throws Exception {
@@ -98,6 +103,9 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection
.when(mDeps)
.newNetworkAgent(any(), any(), any(), any(), any(), any(), any(), any(), any());
+ mVcnNetwork = mock(Network.class);
+ doReturn(mVcnNetwork).when(mNetworkAgent).getNetwork();
+
mGatewayConnection.setUnderlyingNetwork(TEST_UNDERLYING_NETWORK_RECORD_1);
mIkeSession = mGatewayConnection.buildIkeSession(TEST_UNDERLYING_NETWORK_RECORD_1.network);
@@ -166,6 +174,56 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection
assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
}
+ private void verifyDataStallTriggersMigration(
+ UnderlyingNetworkRecord networkRecord,
+ Network networkWithDataStall,
+ boolean expectMobilityUpdate)
+ throws Exception {
+ mGatewayConnection.setUnderlyingNetwork(networkRecord);
+ triggerChildOpened();
+ mTestLooper.dispatchAll();
+
+ final DataStallReport report =
+ new DataStallReport(
+ networkWithDataStall,
+ 1234 /* reportTimestamp */,
+ 1 /* detectionMethod */,
+ new LinkProperties(),
+ new NetworkCapabilities(),
+ new PersistableBundle());
+
+ mGatewayConnection.getConnectivityDiagnosticsCallback().onDataStallSuspected(report);
+ mTestLooper.dispatchAll();
+
+ assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
+
+ if (expectMobilityUpdate) {
+ verify(mIkeSession).setNetwork(networkRecord.network);
+ } else {
+ verify(mIkeSession, never()).setNetwork(any(Network.class));
+ }
+ }
+
+ @Test
+ public void testDataStallTriggersMigration() throws Exception {
+ verifyDataStallTriggersMigration(
+ TEST_UNDERLYING_NETWORK_RECORD_1, mVcnNetwork, true /* expectMobilityUpdate */);
+ }
+
+ @Test
+ public void testDataStallWontTriggerMigrationWhenOnOtherNetwork() throws Exception {
+ verifyDataStallTriggersMigration(
+ TEST_UNDERLYING_NETWORK_RECORD_1,
+ mock(Network.class),
+ false /* expectMobilityUpdate */);
+ }
+
+ @Test
+ public void testDataStallWontTriggerMigrationWhenUnderlyingNetworkLost() throws Exception {
+ verifyDataStallTriggersMigration(
+ null /* networkRecord */, mock(Network.class), false /* expectMobilityUpdate */);
+ }
+
private void verifyVcnTransformsApplied(
VcnGatewayConnection vcnGatewayConnection, boolean expectForwardTransform)
throws Exception {
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
index 6a9a1e22cab1..a4ee2de9f433 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
@@ -24,6 +24,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.vcn.VcnGatewayConnectionConfig.VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY;
import static com.android.server.vcn.VcnGatewayConnection.DUMMY_ADDR;
import static com.android.server.vcn.VcnGatewayConnection.VcnChildSessionConfiguration;
@@ -34,20 +35,25 @@ import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback;
import android.net.IpSecManager;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
import android.net.TelephonyNetworkSpecifier;
+import android.net.vcn.VcnGatewayConnectionConfig;
import android.net.vcn.VcnGatewayConnectionConfigTest;
import android.net.vcn.VcnTransportInfo;
import android.net.wifi.WifiInfo;
@@ -64,6 +70,7 @@ import com.android.server.vcn.routeselection.UnderlyingNetworkRecord;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import java.net.InetAddress;
import java.util.Arrays;
@@ -71,7 +78,9 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.UUID;
+import java.util.concurrent.Executor;
/** Tests for TelephonySubscriptionTracker */
@RunWith(AndroidJUnit4.class)
@@ -287,5 +296,60 @@ public class VcnGatewayConnectionTest extends VcnGatewayConnectionTestBase {
verify(vcnNetworkAgent).unregister();
verifyWakeLockReleased();
+
+ verify(mConnDiagMgr)
+ .unregisterConnectivityDiagnosticsCallback(
+ mGatewayConnection.getConnectivityDiagnosticsCallback());
+ }
+
+ private VcnGatewayConnection buildConnectionWithDataStallHandling(
+ boolean datatStallHandlingEnabled) throws Exception {
+ Set<Integer> options =
+ datatStallHandlingEnabled
+ ? Collections.singleton(
+ VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY)
+ : Collections.emptySet();
+ final VcnGatewayConnectionConfig gatewayConfig =
+ VcnGatewayConnectionConfigTest.buildTestConfigWithGatewayOptions(options);
+ final VcnGatewayConnection gatewayConnection =
+ new VcnGatewayConnection(
+ mVcnContext,
+ TEST_SUB_GRP,
+ TEST_SUBSCRIPTION_SNAPSHOT,
+ gatewayConfig,
+ mGatewayStatusCallback,
+ true /* isMobileDataEnabled */,
+ mDeps);
+ return gatewayConnection;
+ }
+
+ @Test
+ public void testDataStallHandlingEnabled() throws Exception {
+ final VcnGatewayConnection gatewayConnection =
+ buildConnectionWithDataStallHandling(true /* datatStallHandlingEnabled */);
+
+ final ArgumentCaptor<NetworkRequest> networkRequestCaptor =
+ ArgumentCaptor.forClass(NetworkRequest.class);
+ verify(mConnDiagMgr)
+ .registerConnectivityDiagnosticsCallback(
+ networkRequestCaptor.capture(),
+ any(Executor.class),
+ eq(gatewayConnection.getConnectivityDiagnosticsCallback()));
+
+ final NetworkRequest nr = networkRequestCaptor.getValue();
+ final NetworkRequest expected =
+ new NetworkRequest.Builder().addTransportType(TRANSPORT_CELLULAR).build();
+ assertEquals(expected, nr);
+ }
+
+ @Test
+ public void testDataStallHandlingDisabled() throws Exception {
+ buildConnectionWithDataStallHandling(false /* datatStallHandlingEnabled */);
+
+ verify(mConnDiagMgr, never())
+ .registerConnectivityDiagnosticsCallback(
+ any(NetworkRequest.class),
+ any(Executor.class),
+ any(ConnectivityDiagnosticsCallback.class));
}
}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
index 785bff167ad2..7bafd243799f 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -35,6 +35,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.annotation.NonNull;
import android.content.Context;
+import android.net.ConnectivityDiagnosticsManager;
import android.net.ConnectivityManager;
import android.net.InetAddresses;
import android.net.IpSecConfig;
@@ -157,6 +158,7 @@ public class VcnGatewayConnectionTestBase {
@NonNull protected final IpSecService mIpSecSvc;
@NonNull protected final ConnectivityManager mConnMgr;
+ @NonNull protected final ConnectivityDiagnosticsManager mConnDiagMgr;
@NonNull protected final IkeSessionConnectionInfo mIkeConnectionInfo;
@NonNull protected final IkeSessionConfiguration mIkeSessionConfiguration;
@@ -186,6 +188,13 @@ public class VcnGatewayConnectionTestBase {
VcnTestUtils.setupSystemService(
mContext, mConnMgr, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class);
+ mConnDiagMgr = mock(ConnectivityDiagnosticsManager.class);
+ VcnTestUtils.setupSystemService(
+ mContext,
+ mConnDiagMgr,
+ Context.CONNECTIVITY_DIAGNOSTICS_SERVICE,
+ ConnectivityDiagnosticsManager.class);
+
mIkeConnectionInfo =
new IkeSessionConnectionInfo(TEST_ADDR, TEST_ADDR_2, mock(Network.class));
mIkeSessionConfiguration = new IkeSessionConfiguration.Builder(mIkeConnectionInfo).build();