summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobInfo.java5
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobScheduler.java6
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobService.java11
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java51
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java31
-rw-r--r--apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java1
-rw-r--r--apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java18
-rw-r--r--core/api/test-current.txt3
-rw-r--r--core/java/android/app/AppOpsManager.java23
-rw-r--r--core/java/android/app/LocaleConfig.java4
-rw-r--r--core/java/android/app/Notification.java21
-rw-r--r--core/java/android/app/TEST_MAPPING13
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java13
-rw-r--r--core/java/android/app/admin/DevicePolicyResources.java50
-rw-r--r--core/java/android/content/Context.java13
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java17
-rw-r--r--core/java/android/content/pm/PackageParser.java18
-rw-r--r--core/java/android/content/pm/UserInfo.java3
-rw-r--r--core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java30
-rw-r--r--core/java/android/hardware/ISensorPrivacyManager.aidl2
-rw-r--r--core/java/android/hardware/SensorPrivacyManager.java19
-rw-r--r--core/java/android/hardware/camera2/CameraCharacteristics.java38
-rw-r--r--core/java/android/hardware/camera2/CameraMetadata.java106
-rw-r--r--core/java/android/hardware/camera2/CaptureRequest.java28
-rw-r--r--core/java/android/hardware/camera2/CaptureResult.java38
-rw-r--r--core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java129
-rw-r--r--core/java/android/hardware/camera2/params/DynamicRangeProfiles.java4
-rw-r--r--core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java11
-rw-r--r--core/java/android/hardware/input/InputManagerInternal.java9
-rw-r--r--core/java/android/inputmethodservice/IInputMethodWrapper.java15
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java70
-rw-r--r--core/java/android/os/Environment.java12
-rw-r--r--core/java/android/os/UserManager.java5
-rw-r--r--core/java/android/provider/CallLog.java10
-rw-r--r--core/java/android/provider/DeviceConfig.java8
-rw-r--r--core/java/android/provider/Settings.java34
-rw-r--r--core/java/android/service/quickaccesswallet/TEST_MAPPING2
-rw-r--r--core/java/android/util/FeatureFlagUtils.java11
-rw-r--r--core/java/android/view/SurfaceView.java218
-rw-r--r--core/java/android/view/View.java43
-rw-r--r--core/java/android/view/ViewRootImpl.java77
-rw-r--r--core/java/android/view/autofill/AutofillManager.java61
-rw-r--r--core/java/android/view/inputmethod/InputMethod.java8
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java26
-rw-r--r--core/java/android/view/translation/UiTranslationController.java7
-rw-r--r--core/java/android/window/ImeOnBackInvokedDispatcher.aidl20
-rw-r--r--core/java/android/window/ImeOnBackInvokedDispatcher.java187
-rw-r--r--core/java/android/window/OnBackInvokedDispatcher.java15
-rw-r--r--core/java/android/window/ProxyOnBackInvokedDispatcher.java16
-rw-r--r--core/java/android/window/WindowOnBackInvokedDispatcher.java37
-rw-r--r--core/java/android/window/WindowTokenClient.java28
-rw-r--r--core/java/com/android/internal/app/ChooserActivity.java23
-rw-r--r--core/java/com/android/internal/app/ChooserActivityLogger.java3
-rw-r--r--core/java/com/android/internal/app/ChooserActivityLoggerImpl.java6
-rw-r--r--core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java2
-rw-r--r--core/java/com/android/internal/app/UnlaunchableAppActivity.java7
-rw-r--r--core/java/com/android/internal/logging/UiEventLoggerImpl.java6
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBarService.aidl2
-rw-r--r--core/java/com/android/internal/util/ImageUtils.java16
-rw-r--r--core/java/com/android/internal/view/IInputMethod.aidl4
-rw-r--r--core/java/com/android/internal/view/IInputMethodManager.aidl3
-rw-r--r--core/jni/android_graphics_BLASTBufferQueue.cpp61
-rw-r--r--core/res/AndroidManifest.xml4
-rw-r--r--core/res/OWNERS1
-rw-r--r--core/res/res/values/config.xml2
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ChooserActivityLoggerFake.java10
-rw-r--r--data/etc/services.core.protolog.json12
-rw-r--r--data/keyboards/Vendor_0957_Product_0001.kl42
-rw-r--r--graphics/java/android/graphics/BLASTBufferQueue.java10
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java21
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java40
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java73
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAppOpsListener.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAppOpsListener.java)3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java48
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java44
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java315
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java114
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java39
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java18
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithmTest.java96
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java8
-rw-r--r--libs/hwui/pipeline/skia/ShaderCache.cpp41
-rw-r--r--libs/hwui/pipeline/skia/ShaderCache.h9
-rw-r--r--libs/hwui/tests/unit/ShaderCacheTests.cpp10
-rw-r--r--libs/input/PointerController.cpp7
-rw-r--r--libs/input/PointerController.h1
-rw-r--r--libs/input/PointerControllerContext.h1
-rw-r--r--libs/input/tests/PointerController_test.cpp40
-rw-r--r--media/java/android/media/ImageReader.java9
-rw-r--r--packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java8
-rw-r--r--packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java4
-rw-r--r--packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_content_layout.xml2
-rw-r--r--packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java3
-rw-r--r--packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java16
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java5
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java25
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java24
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java22
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java38
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java76
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java9
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java10
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java3
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java5
-rw-r--r--packages/SystemUI/res-keyguard/values-sw600dp-land/dimens.xml4
-rw-r--r--packages/SystemUI/res-product/values/strings.xml4
-rw-r--r--packages/SystemUI/res/layout/alert_dialog_systemui.xml124
-rw-r--r--packages/SystemUI/res/layout/clipboard_overlay.xml26
-rw-r--r--packages/SystemUI/res/layout/fgs_manager_app_item.xml3
-rw-r--r--packages/SystemUI/res/layout/qs_paged_page.xml4
-rw-r--r--packages/SystemUI/res/layout/scrollable_alert_dialog_systemui.xml25
-rw-r--r--packages/SystemUI/res/values-sw600dp-land/dimens.xml13
-rw-r--r--packages/SystemUI/res/values-sw720dp-land/dimens.xml6
-rw-r--r--packages/SystemUI/res/values/dimens.xml16
-rw-r--r--packages/SystemUI/res/values/strings.xml10
-rw-r--r--packages/SystemUI/res/values/styles.xml10
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt197
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java9
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java9
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java29
-rw-r--r--packages/SystemUI/src/com/android/keyguard/LockIconView.java4
-rw-r--r--packages/SystemUI/src/com/android/keyguard/LockIconViewController.java20
-rw-r--r--packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java62
-rw-r--r--packages/SystemUI/src/com/android/keyguard/NumPadButton.java2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/NumPadKey.java2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/ScreenDecorations.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmProvider.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java84
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java (renamed from packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java)22
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java142
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java124
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/UserContextProvider.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt54
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/Utils.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt193
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt9
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java30
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaViewControllerTest.kt20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt55
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt35
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java65
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java28
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java71
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java71
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java71
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java45
-rw-r--r--services/autofill/java/com/android/server/autofill/Session.java45
-rw-r--r--services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java27
-rw-r--r--services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java4
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java18
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java56
-rw-r--r--services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java101
-rw-r--r--services/companion/java/com/android/server/companion/virtual/InputController.java219
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java3
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java14
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java3
-rw-r--r--services/core/java/com/android/server/am/AppBatteryTracker.java3
-rw-r--r--services/core/java/com/android/server/am/AppRestrictionController.java49
-rw-r--r--services/core/java/com/android/server/am/CachedAppOptimizer.java100
-rw-r--r--services/core/java/com/android/server/am/ConnectionRecord.java6
-rw-r--r--services/core/java/com/android/server/am/DropboxRateLimiter.java11
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java38
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java9
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java8
-rw-r--r--services/core/java/com/android/server/audio/SpatializerHelper.java6
-rw-r--r--services/core/java/com/android/server/biometrics/AuthService.java7
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java1
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java2
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java21
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java147
-rw-r--r--services/core/java/com/android/server/input/NativeInputManagerService.java6
-rw-r--r--services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java8
-rw-r--r--services/core/java/com/android/server/inputmethod/HandwritingModeController.java27
-rw-r--r--services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java6
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java57
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecord.java68
-rw-r--r--services/core/java/com/android/server/notification/NotificationDelegate.java10
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerInternal.java3
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java136
-rw-r--r--services/core/java/com/android/server/notification/PermissionHelper.java13
-rw-r--r--services/core/java/com/android/server/notification/PreferencesHelper.java18
-rw-r--r--services/core/java/com/android/server/notification/ReviewNotificationPermissionsJobService.java79
-rw-r--r--services/core/java/com/android/server/notification/ReviewNotificationPermissionsReceiver.java122
-rw-r--r--services/core/java/com/android/server/pm/ApkChecksums.java15
-rw-r--r--services/core/java/com/android/server/pm/AppIdSettingMap.java28
-rw-r--r--services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java7
-rw-r--r--services/core/java/com/android/server/pm/Installer.java23
-rw-r--r--services/core/java/com/android/server/pm/Settings.java2
-rw-r--r--services/core/java/com/android/server/pm/ShortcutPackage.java360
-rw-r--r--services/core/java/com/android/server/pm/ShortcutPackageItem.java37
-rw-r--r--services/core/java/com/android/server/pm/ShortcutService.java76
-rw-r--r--services/core/java/com/android/server/pm/ShortcutUser.java1
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java11
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java6
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java15
-rw-r--r--services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java10
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerService.java6
-rw-r--r--services/core/java/com/android/server/trust/TrustAgentWrapper.java27
-rw-r--r--services/core/java/com/android/server/trust/TrustManagerService.java17
-rw-r--r--services/core/java/com/android/server/utils/WatchedArrayList.java2
-rw-r--r--services/core/java/com/android/server/wm/AccessibilityController.java37
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java28
-rw-r--r--services/core/java/com/android/server/wm/BLASTSyncEngine.java21
-rw-r--r--services/core/java/com/android/server/wm/BackNavigationController.java10
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java4
-rw-r--r--services/core/java/com/android/server/wm/InputManagerCallback.java16
-rw-r--r--services/core/java/com/android/server/wm/InputMonitor.java12
-rw-r--r--services/core/java/com/android/server/wm/RecentsAnimationController.java17
-rw-r--r--services/core/java/com/android/server/wm/SurfaceAnimationRunner.java16
-rw-r--r--services/core/java/com/android/server/wm/Task.java8
-rw-r--r--services/core/java/com/android/server/wm/TaskDisplayArea.java25
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java10
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerInternal.java38
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java85
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java4
-rw-r--r--services/core/java/com/android/server/wm/WindowToken.java2
-rw-r--r--services/core/jni/com_android_server_am_CachedAppOptimizer.cpp245
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp69
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java25
-rw-r--r--services/midi/java/com/android/server/midi/MidiService.java27
-rw-r--r--services/proguard.flags5
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java64
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/am/DropboxRateLimiterTest.java16
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java27
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java101
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java16
-rw-r--r--services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt245
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java20
-rw-r--r--services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java4
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java108
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java86
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java104
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsReceiverTest.java206
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java28
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java1
-rw-r--r--services/voiceinteraction/TEST_MAPPING13
-rw-r--r--telecomm/java/android/telecom/PhoneAccountHandle.java33
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java19
-rw-r--r--telephony/java/android/telephony/DataFailCause.java5
-rw-r--r--tests/ApkVerityTest/Android.bp4
-rw-r--r--tests/ApkVerityTest/block_device_writer/Android.bp24
-rw-r--r--tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt34
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt4
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml10
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml4
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java5
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeStateInitializeActivity.java50
-rw-r--r--tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt11
335 files changed, 7131 insertions, 2580 deletions
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index f49cdbf403f0..4710322db283 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -1777,7 +1777,10 @@ public class JobInfo implements Parcelable {
* {@link Build.VERSION_CODES#S}, but starting from Android version
* {@link Build.VERSION_CODES#TIRAMISU}, expedited jobs for the foreground app are
* guaranteed to be started before {@link JobScheduler#schedule(JobInfo)} returns (assuming
- * all requested constraints are satisfied), similar to foreground services.
+ * all requested constraints are satisfied), similar to foreground services. However, this
+ * start guarantee means there is a higher chance of overlapping executions, as noted in
+ * {@link JobService}, so be sure to handle that properly if you intend to reschedule the
+ * job while it's actively running.
*
* @see JobInfo#isExpedited()
*/
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
index 1f4ef0470ebd..388bbf1b26a0 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
@@ -107,7 +107,9 @@ public abstract class JobScheduler {
/**
* Schedule a job to be executed. Will replace any currently scheduled job with the same
* ID with the new information in the {@link JobInfo}. If a job with the given ID is currently
- * running, it will be stopped.
+ * running, it will be stopped. Note that in some cases, the newly scheduled job may be started
+ * before the previously running job has been fully stopped. See {@link JobService} for
+ * additional details.
*
* <p class="caution"><strong>Note:</strong> Scheduling a job can have a high cost, even if it's
* rescheduling the same job and the job didn't execute, especially on platform versions before
@@ -131,7 +133,7 @@ public abstract class JobScheduler {
* job. If a job with the same ID is already scheduled, it will be replaced with the
* new {@link JobInfo}, but any previously enqueued work will remain and be dispatched the
* next time it runs. If a job with the same ID is already running, the new work will be
- * enqueued for it.
+ * enqueued for it without stopping the job.
*
* <p>The work you enqueue is later retrieved through
* {@link JobParameters#dequeueWork() JobParameters.dequeueWork}. Be sure to see there
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobService.java b/apex/jobscheduler/framework/java/android/app/job/JobService.java
index e5b07429a5c6..7ed4b62ae7e4 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobService.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobService.java
@@ -31,6 +31,17 @@ import android.os.IBinder;
* in blocking any future callbacks from the JobManager - specifically
* {@link #onStopJob(android.app.job.JobParameters)}, which is meant to inform you that the
* scheduling requirements are no longer being met.</p>
+ *
+ * As a subclass of {@link Service}, there will only be one active instance of any JobService
+ * subclasses, regardless of job ID. This means that if you schedule multiple jobs with different
+ * job IDs but using the same JobService class, that JobService may receive multiple calls to
+ * {@link #onStartJob(JobParameters)} and {@link #onStopJob(JobParameters)}, with each call being
+ * for the separate jobs.
+ *
+ * <p class="note">Note that if you cancel and reschedule an already executing job,
+ * there may be a small period of time where {@link #onStartJob(JobParameters)} has been called for
+ * the newly scheduled job instance before {@link #onStopJob(JobParameters)} has been called or
+ * fully processed for the old job.</p>
*/
public abstract class JobService extends Service {
private static final String TAG = "JobService";
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 c86353c84467..afe36b5fa25a 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -542,6 +542,22 @@ class JobConcurrencyManager {
return mRunningJobs.contains(job);
}
+ /**
+ * Returns true if a job that is "similar" to the provided job is currently running.
+ * "Similar" in this context means any job that the {@link JobStore} would consider equivalent
+ * and replace one with the other.
+ */
+ @GuardedBy("mLock")
+ private boolean isSimilarJobRunningLocked(JobStatus job) {
+ for (int i = mRunningJobs.size() - 1; i >= 0; --i) {
+ JobStatus js = mRunningJobs.valueAt(i);
+ if (job.getUid() == js.getUid() && job.getJobId() == js.getJobId()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/** Return {@code true} if the state was updated. */
@GuardedBy("mLock")
private boolean refreshSystemStateLocked() {
@@ -699,6 +715,21 @@ class JobConcurrencyManager {
continue;
}
+ final boolean isTopEj = nextPending.shouldTreatAsExpeditedJob()
+ && nextPending.lastEvaluatedBias == JobInfo.BIAS_TOP_APP;
+ // Avoid overlapping job execution as much as possible.
+ if (!isTopEj && isSimilarJobRunningLocked(nextPending)) {
+ if (DEBUG) {
+ Slog.w(TAG, "Delaying execution of job because of similarly running one: "
+ + nextPending);
+ }
+ // It would be nice to let the JobService running the other similar job know about
+ // this new job so that it doesn't unbind from the JobService and we can call
+ // onStartJob as soon as the older job finishes.
+ // TODO: optimize the job reschedule flow to reduce service binding churn
+ continue;
+ }
+
// Find an available slot for nextPending. The context should be one of the following:
// 1. Unused
// 2. Its job should have used up its minimum execution guarantee so it
@@ -707,8 +738,6 @@ class JobConcurrencyManager {
ContextAssignment selectedContext = null;
final int allWorkTypes = getJobWorkTypes(nextPending);
final boolean pkgConcurrencyOkay = !isPkgConcurrencyLimitedLocked(nextPending);
- final boolean isTopEj = nextPending.shouldTreatAsExpeditedJob()
- && nextPending.lastEvaluatedBias == JobInfo.BIAS_TOP_APP;
final boolean isInOverage = projectedRunningCount > STANDARD_CONCURRENCY_LIMIT;
boolean startingJob = false;
if (idle.size() > 0) {
@@ -1177,6 +1206,15 @@ class JobConcurrencyManager {
continue;
}
+ // Avoid overlapping job execution as much as possible.
+ if (isSimilarJobRunningLocked(nextPending)) {
+ if (DEBUG) {
+ Slog.w(TAG, "Avoiding execution of job because of similarly running one: "
+ + nextPending);
+ }
+ continue;
+ }
+
if (worker.getPreferredUid() != nextPending.getUid()) {
if (backupJob == null && !isPkgConcurrencyLimitedLocked(nextPending)) {
int allWorkTypes = getJobWorkTypes(nextPending);
@@ -1260,6 +1298,15 @@ class JobConcurrencyManager {
continue;
}
+ // Avoid overlapping job execution as much as possible.
+ if (isSimilarJobRunningLocked(nextPending)) {
+ if (DEBUG) {
+ Slog.w(TAG, "Avoiding execution of job because of similarly running one: "
+ + nextPending);
+ }
+ continue;
+ }
+
if (isPkgConcurrencyLimitedLocked(nextPending)) {
continue;
}
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 358c32722b8b..cd70e88b18aa 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1208,12 +1208,22 @@ public class JobSchedulerService extends com.android.server.SystemService
// This may throw a SecurityException.
jobStatus.prepareLocked();
+ final boolean canExecuteImmediately;
if (toCancel != null) {
// Implicitly replaces the existing job record with the new instance
- cancelJobImplLocked(toCancel, jobStatus, JobParameters.STOP_REASON_CANCELLED_BY_APP,
- JobParameters.INTERNAL_STOP_REASON_CANCELED, "job rescheduled by app");
+ final boolean wasJobExecuting = cancelJobImplLocked(toCancel, jobStatus,
+ JobParameters.STOP_REASON_CANCELLED_BY_APP,
+ JobParameters.INTERNAL_STOP_REASON_CANCELED,
+ "job rescheduled by app");
+ // Avoid overlapping job executions. Don't push for immediate execution if an old
+ // job with the same ID was running, but let TOP EJs start immediately.
+ canExecuteImmediately = !wasJobExecuting
+ || (jobStatus.isRequestedExpeditedJob()
+ && mUidBiasOverride.get(jobStatus.getSourceUid(), JobInfo.BIAS_DEFAULT)
+ == JobInfo.BIAS_TOP_APP);
} else {
startTrackingJobLocked(jobStatus, null);
+ canExecuteImmediately = true;
}
if (work != null) {
@@ -1256,7 +1266,12 @@ public class JobSchedulerService extends com.android.server.SystemService
// list and try to run it.
mJobPackageTracker.notePending(jobStatus);
mPendingJobQueue.add(jobStatus);
- maybeRunPendingJobsLocked();
+ if (canExecuteImmediately) {
+ // Don't ask the JobConcurrencyManager to try to run the job immediately. The
+ // JobServiceContext will ask the JobConcurrencyManager for another job once
+ // it finishes cleaning up the old job.
+ maybeRunPendingJobsLocked();
+ }
} else {
evaluateControllerStatesLocked(jobStatus);
}
@@ -1377,8 +1392,10 @@ public class JobSchedulerService extends com.android.server.SystemService
* is null, the cancelled job is removed outright from the system. If
* {@code incomingJob} is non-null, it replaces {@code cancelled} in the store of
* currently scheduled jobs.
+ *
+ * @return true if the cancelled job was running
*/
- private void cancelJobImplLocked(JobStatus cancelled, JobStatus incomingJob,
+ private boolean cancelJobImplLocked(JobStatus cancelled, JobStatus incomingJob,
@JobParameters.StopReason int reason, int internalReasonCode, String debugReason) {
if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString());
cancelled.unprepareLocked();
@@ -1389,7 +1406,7 @@ public class JobSchedulerService extends com.android.server.SystemService
}
mChangedJobList.remove(cancelled);
// Cancel if running.
- mConcurrencyManager.stopJobOnServiceContextLocked(
+ boolean wasRunning = mConcurrencyManager.stopJobOnServiceContextLocked(
cancelled, reason, internalReasonCode, debugReason);
// If this is a replacement, bring in the new version of the job
if (incomingJob != null) {
@@ -1397,6 +1414,7 @@ public class JobSchedulerService extends com.android.server.SystemService
startTrackingJobLocked(incomingJob, cancelled);
}
reportActiveLocked();
+ return wasRunning;
}
void updateUidState(int uid, int procState) {
@@ -1755,7 +1773,7 @@ public class JobSchedulerService extends com.android.server.SystemService
// same job ID), we remove it from the JobStore and tell the JobServiceContext to stop
// running the job. Once the job stops running, we then call this method again.
// TODO: rework code so we don't intentionally call this method twice for the same job
- Slog.w(TAG, "Job didn't exist in JobStore");
+ Slog.w(TAG, "Job didn't exist in JobStore: " + jobStatus.toShortString());
}
if (mReadyToRock) {
for (int i = 0; i < mControllers.size(); i++) {
@@ -3813,6 +3831,7 @@ public class JobSchedulerService extends com.android.server.SystemService
// Double indent for readability
pw.increaseIndent();
pw.increaseIndent();
+ pw.println(job.toShortString());
job.dump(pw, true, nowElapsed);
pw.decreaseIndent();
pw.decreaseIndent();
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
index 80f3fea1907c..c90291e5f264 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
@@ -979,6 +979,7 @@ public class AppIdleHistory {
dumpBucketExpiryTimes(idpw, appUsageHistory, totalElapsedTime);
idpw.print(" lastJob=");
TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastJobRunTime, idpw);
+ idpw.print(" lastInformedBucket=" + appUsageHistory.lastInformedBucket);
if (appUsageHistory.lastRestrictAttemptElapsedTime > 0) {
idpw.print(" lastRestrictAttempt=");
TimeUtils.formatDuration(
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index c3d6b738fc1e..1891e06a9420 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -1155,6 +1155,12 @@ public class AppStandbyController
final int appId = getAppId(packageName);
if (appId < 0) return;
+ final int minBucket = getAppMinBucket(packageName, appId, userId);
+ if (idle && minBucket < AppIdleHistory.IDLE_BUCKET_CUTOFF) {
+ Slog.e(TAG, "Tried to force an app to be idle when its min bucket is "
+ + standbyBucketToString(minBucket));
+ return;
+ }
final long elapsedRealtime = mInjector.elapsedRealtime();
final boolean previouslyIdle = isAppIdleFiltered(packageName, appId,
@@ -1166,12 +1172,10 @@ public class AppStandbyController
final boolean stillIdle = isAppIdleFiltered(packageName, appId,
userId, elapsedRealtime);
// Inform listeners if necessary
+ maybeInformListeners(packageName, userId, elapsedRealtime, standbyBucket,
+ REASON_MAIN_FORCED_BY_USER, false);
if (previouslyIdle != stillIdle) {
- maybeInformListeners(packageName, userId, elapsedRealtime, standbyBucket,
- REASON_MAIN_FORCED_BY_USER, false);
- if (!stillIdle) {
- notifyBatteryStats(packageName, userId, idle);
- }
+ notifyBatteryStats(packageName, userId, stillIdle);
}
}
@@ -1934,6 +1938,8 @@ public class AppStandbyController
}
mAppIdleHistory.setAppStandbyBucket(
packageName, userId, elapsedRealtime, newBucket, newReason);
+ maybeInformListeners(packageName, userId, elapsedRealtime, newBucket,
+ newReason, false);
}
}
@@ -2490,6 +2496,8 @@ public class AppStandbyController
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_INFORM_LISTENERS:
+ // TODO(230875908): Properly notify BatteryStats when apps change from active to
+ // idle, and vice versa
StandbyUpdateRecord r = (StandbyUpdateRecord) msg.obj;
informListeners(r.packageName, r.userId, r.bucket, r.reason,
r.isUserInteraction);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 4681d4943256..2c0b6e9ee89d 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -800,6 +800,7 @@ package android.content.pm {
method public boolean hasRequestForegroundServiceExemption();
method public boolean isPrivilegedApp();
method public boolean isSystemApp();
+ method public void setEnableOnBackInvokedCallback(boolean);
field public static final int PRIVATE_FLAG_PRIVILEGED = 8; // 0x8
field public int privateFlags;
}
@@ -919,7 +920,7 @@ package android.content.pm {
field public int id;
field public String lastLoggedInFingerprint;
field public long lastLoggedInTime;
- field public String name;
+ field @Nullable public String name;
field public boolean partial;
field public boolean preCreated;
field public int profileBadge;
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index c7c654a0b071..3b1943bf86f6 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2537,8 +2537,8 @@ public class AppOpsManager {
* restriction} for a certain app-op.
*/
private static RestrictionBypass[] sOpAllowSystemRestrictionBypass = new RestrictionBypass[] {
- null, //COARSE_LOCATION
- null, //FINE_LOCATION
+ new RestrictionBypass(true, false, false), //COARSE_LOCATION
+ new RestrictionBypass(true, false, false), //FINE_LOCATION
null, //GPS
null, //VIBRATE
null, //READ_CONTACTS
@@ -2547,7 +2547,7 @@ public class AppOpsManager {
null, //WRITE_CALL_LOG
null, //READ_CALENDAR
null, //WRITE_CALENDAR
- new RestrictionBypass(true, false), //WIFI_SCAN
+ new RestrictionBypass(false, true, false), //WIFI_SCAN
null, //POST_NOTIFICATION
null, //NEIGHBORING_CELLS
null, //CALL_PHONE
@@ -2561,10 +2561,10 @@ public class AppOpsManager {
null, //READ_ICC_SMS
null, //WRITE_ICC_SMS
null, //WRITE_SETTINGS
- new RestrictionBypass(true, false), //SYSTEM_ALERT_WINDOW
+ new RestrictionBypass(false, true, false), //SYSTEM_ALERT_WINDOW
null, //ACCESS_NOTIFICATIONS
null, //CAMERA
- new RestrictionBypass(false, true), //RECORD_AUDIO
+ new RestrictionBypass(false, false, true), //RECORD_AUDIO
null, //PLAY_AUDIO
null, //READ_CLIPBOARD
null, //WRITE_CLIPBOARD
@@ -2582,7 +2582,7 @@ public class AppOpsManager {
null, //MONITOR_HIGH_POWER_LOCATION
null, //GET_USAGE_STATS
null, //MUTE_MICROPHONE
- new RestrictionBypass(true, false), //TOAST_WINDOW
+ new RestrictionBypass(false, true, false), //TOAST_WINDOW
null, //PROJECT_MEDIA
null, //ACTIVATE_VPN
null, //WALLPAPER
@@ -2614,7 +2614,7 @@ public class AppOpsManager {
null, // ACCEPT_HANDOVER
null, // MANAGE_IPSEC_HANDOVERS
null, // START_FOREGROUND
- new RestrictionBypass(true, false), // BLUETOOTH_SCAN
+ new RestrictionBypass(false, true, false), // BLUETOOTH_SCAN
null, // USE_BIOMETRIC
null, // ACTIVITY_RECOGNITION
null, // SMS_FINANCIAL_TRANSACTIONS
@@ -3331,6 +3331,9 @@ public class AppOpsManager {
* @hide
*/
public static class RestrictionBypass {
+ /** Does the app need to be system uid to bypass the restriction */
+ public boolean isSystemUid;
+
/** Does the app need to be privileged to bypass the restriction */
public boolean isPrivileged;
@@ -3340,12 +3343,14 @@ public class AppOpsManager {
*/
public boolean isRecordAudioRestrictionExcept;
- public RestrictionBypass(boolean isPrivileged, boolean isRecordAudioRestrictionExcept) {
+ public RestrictionBypass(boolean isSystemUid, boolean isPrivileged,
+ boolean isRecordAudioRestrictionExcept) {
+ this.isSystemUid = isSystemUid;
this.isPrivileged = isPrivileged;
this.isRecordAudioRestrictionExcept = isRecordAudioRestrictionExcept;
}
- public static RestrictionBypass UNRESTRICTED = new RestrictionBypass(true, true);
+ public static RestrictionBypass UNRESTRICTED = new RestrictionBypass(false, true, true);
}
/**
diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java
index 436eac37b4fb..7c83d5850f76 100644
--- a/core/java/android/app/LocaleConfig.java
+++ b/core/java/android/app/LocaleConfig.java
@@ -45,7 +45,9 @@ import java.util.Set;
* referenced in the manifest via {@code android:localeConfig} on
* {@code <application>}.
*
- * For more information, see TODO(b/214154050): add link to guide
+ * <p>For more information, see
+ * <a href="https://developer.android.com/about/versions/13/features/app-languages#use-localeconfig">
+ * the section on per-app language preferences</a>.
*
* @attr ref android.R.styleable#LocaleConfig_Locale_name
* @attr ref android.R.styleable#AndroidManifestApplication_localeConfig
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 974d20a7eab3..e82073380394 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -7770,10 +7770,11 @@ public class Notification implements Parcelable
* user will always see the normal notification view.
*
* <p>
- * If the app is targeting Android P and above, it is required to use the {@link Person}
- * class in order to get an optimal rendering of the notification and its avatars. For
- * conversations involving multiple people, the app should also make sure that it marks the
- * conversation as a group with {@link #setGroupConversation(boolean)}.
+ * If the app is targeting Android {@link android.os.Build.VERSION_CODES#P} and above, it is
+ * required to use the {@link Person} class in order to get an optimal rendering of the
+ * notification and its avatars. For conversations involving multiple people, the app should
+ * also make sure that it marks the conversation as a group with
+ * {@link #setGroupConversation(boolean)}.
*
* <p>
* This class is a "rebuilder": It attaches to a Builder object and modifies its behavior.
@@ -7846,8 +7847,8 @@ public class Notification implements Parcelable
* @param user Required - The person displayed for any messages that are sent by the
* user. Any messages added with {@link #addMessage(Notification.MessagingStyle.Message)}
* who don't have a Person associated with it will be displayed as if they were sent
- * by this user. The user also needs to have a valid name associated with it, which will
- * be enforced starting in Android P.
+ * by this user. The user also needs to have a valid name associated with it, which is
+ * enforced starting in Android {@link android.os.Build.VERSION_CODES#P}.
*/
public MessagingStyle(@NonNull Person user) {
mUser = user;
@@ -7904,9 +7905,9 @@ public class Notification implements Parcelable
/**
* Sets the title to be displayed on this conversation. May be set to {@code null}.
*
- * <p>Starting in {@link Build.VERSION_CODES#R, this conversation title will be ignored if a
- * valid shortcutId is added via {@link Notification.Builder#setShortcutId(String)}. In this
- * case, {@link ShortcutInfo#getLongLabel()} (or, if missing,
+ * <p>Starting in {@link Build.VERSION_CODES#R}, this conversation title will be ignored
+ * if a valid shortcutId is added via {@link Notification.Builder#setShortcutId(String)}.
+ * In this case, {@link ShortcutInfo#getLongLabel()} (or, if missing,
* {@link ShortcutInfo#getShortLabel()}) will be shown as the conversation title
* instead.
*
@@ -8065,7 +8066,7 @@ public class Notification implements Parcelable
}
/**
- * Gets the list of {@code Message} objects that represent the notification
+ * Gets the list of {@code Message} objects that represent the notification.
*/
public List<Message> getMessages() {
return mMessages;
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index a8ae1913b964..32207af22dc1 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -110,19 +110,6 @@
"options": [
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
- },
- // TODO(b/225076204): Remove the following four test cases after fixing the test fail.
- {
- "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_createDetectorTwiceQuickly_triggerSuccess"
- },
- {
- "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_onDetectFromExternalSource_success"
- },
- {
- "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_createDetectorTwiceQuickly_triggerSuccess"
- },
- {
- "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_onDetectFromMic_success"
}
],
"file_patterns": ["(/|^)VoiceInteract[^/]*"]
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 315bd71f8c0c..0a2b42121545 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1811,10 +1811,6 @@ public class DevicePolicyManager {
* #EXTRA_PROVISIONING_ALLOWED_PROVISIONING_MODES} array extra contain {@link
* #PROVISIONING_MODE_MANAGED_PROFILE} and {@link #PROVISIONING_MODE_FULLY_MANAGED_DEVICE}.
*
- * <p>Also, if this flag is set, the admin app's {@link #ACTION_GET_PROVISIONING_MODE} activity
- * will not receive the {@link #EXTRA_PROVISIONING_IMEI} and {@link
- * #EXTRA_PROVISIONING_SERIAL_NUMBER} extras.
- *
* <p>This flag can be combined with {@link #FLAG_SUPPORTED_MODES_PERSONALLY_OWNED}. In
* that case, the admin app's {@link #ACTION_GET_PROVISIONING_MODE} activity will have
* the {@link #EXTRA_PROVISIONING_ALLOWED_PROVISIONING_MODES} array extra contain {@link
@@ -1834,6 +1830,10 @@ public class DevicePolicyManager {
* activity to have the {@link #EXTRA_PROVISIONING_ALLOWED_PROVISIONING_MODES} array extra
* contain only {@link #PROVISIONING_MODE_MANAGED_PROFILE}.
*
+ * <p>Also, if this flag is set, the admin app's {@link #ACTION_GET_PROVISIONING_MODE} activity
+ * will not receive the {@link #EXTRA_PROVISIONING_IMEI} and {@link
+ * #EXTRA_PROVISIONING_SERIAL_NUMBER} extras.
+ *
* <p>This flag can be combined with {@link #FLAG_SUPPORTED_MODES_ORGANIZATION_OWNED}. In
* that case, the admin app's {@link #ACTION_GET_PROVISIONING_MODE} activity will have the
* {@link #EXTRA_PROVISIONING_ALLOWED_PROVISIONING_MODES} array extra contain {@link
@@ -13249,8 +13249,9 @@ public class DevicePolicyManager {
* Called by a device owner, profile owner of a managed profile or delegated app with
* {@link #DELEGATION_NETWORK_LOGGING} to control the network logging feature.
*
- * <p> When network logging is enabled by a profile owner, the network logs will only include
- * work profile network activity, not activity on the personal profile.
+ * <p> Supported for a device owner from Android 8. Supported for a profile owner of a managed
+ * profile from Android 12. When network logging is enabled by a profile owner, the network logs
+ * will only include work profile network activity, not activity on the personal profile.
*
* <p> Network logs contain DNS lookup and connect() library call events. The following library
* functions are recorded while network logging is active:
diff --git a/core/java/android/app/admin/DevicePolicyResources.java b/core/java/android/app/admin/DevicePolicyResources.java
index 08056847e12e..c8033fafbc4e 100644
--- a/core/java/android/app/admin/DevicePolicyResources.java
+++ b/core/java/android/app/admin/DevicePolicyResources.java
@@ -863,12 +863,6 @@ public final class DevicePolicyResources {
PREFIX + "REMOVE_AND_UNINSTALL_DEVICE_ADMIN";
/**
- * Title for selecting device admin apps
- */
- public static final String SELECT_DEVICE_ADMIN_APPS =
- PREFIX + "SELECT_DEVICE_ADMIN_APPS";
-
- /**
* Message when there are no available device admin apps to display
*/
public static final String NO_DEVICE_ADMINS = PREFIX + "NO_DEVICE_ADMINS";
@@ -910,11 +904,6 @@ public final class DevicePolicyResources {
PREFIX + "ACTIVE_DEVICE_ADMIN_WARNING";
/**
- * Title for screen to set a profile owner
- */
- public static final String SET_PROFILE_OWNER_TITLE = PREFIX + "SET_PROFILE_OWNER_TITLE";
-
- /**
* Simplified title for dialog to set a profile owner
*/
public static final String SET_PROFILE_OWNER_DIALOG_TITLE =
@@ -1173,7 +1162,8 @@ public final class DevicePolicyResources {
/**
* Header for items under the personal user
*/
- public static final String PERSONAL_CATEGORY_HEADER = PREFIX + "CATEGORY_PERSONAL";
+ public static final String PERSONAL_CATEGORY_HEADER =
+ PREFIX + "PERSONAL_CATEGORY_HEADER";
/**
* Text to indicate work notification content will be shown on the lockscreen.
@@ -1196,8 +1186,7 @@ public final class DevicePolicyResources {
/**
* Text for toggle to enable auto-sycing work data
*/
- public static final String AUTO_SYNC_WORK_DATA = PREFIX
- + "AUTO_SYNC_WORK_DATA";
+ public static final String AUTO_SYNC_WORK_DATA = PREFIX + "AUTO_SYNC_WORK_DATA";
/**
* Summary for "More security settings" section when a work profile is on the device.
@@ -1213,10 +1202,17 @@ public final class DevicePolicyResources {
+ "LOCK_SETTINGS_NEW_PROFILE_LOCK_TITLE";
/**
+ * Title for screen asking the user to update the type of screen lock (such as a
+ * pattern, PIN, or password) that they need to enter to use their work apps
+ */
+ public static final String LOCK_SETTINGS_UPDATE_PROFILE_LOCK_TITLE = PREFIX
+ + "LOCK_SETTINGS_UPDATE_PROFILE_LOCK_TITLE";
+
+ /**
* Title for section listing information that can be seen by organization
*/
public static final String INFORMATION_SEEN_BY_ORGANIZATION_TITLE = PREFIX
- + "information_seen_by_organization_title";
+ + "INFORMATION_SEEN_BY_ORGANIZATION_TITLE";
/**
* Title for section listing changes made by the organization.
@@ -1229,6 +1225,30 @@ public final class DevicePolicyResources {
*/
public static final String ENTERPRISE_PRIVACY_FOOTER =
PREFIX + "ENTERPRISE_PRIVACY_FOOTER";
+
+ /**
+ * Title for spell checker settings for work.
+ */
+ public static final String SPELL_CHECKER_FOR_WORK =
+ PREFIX + "SPELL_CHECKER_FOR_WORK";
+
+ /**
+ * Title for personal dictionary for work settings.
+ */
+ public static final String PERSONAL_DICTIONARY_FOR_WORK =
+ PREFIX + "PERSONAL_DICTIONARY_FOR_WORK";
+
+ /**
+ * Summary for switch preference to indicate it is disabled by the admin
+ */
+ public static final String DISABLED_BY_ADMIN_SWITCH_SUMMARY =
+ PREFIX + "DISABLED_BY_ADMIN_SWITCH_SUMMARY";
+
+ /**
+ * Summary for switch preference to indicate it is enabled by the admin
+ */
+ public static final String ENABLED_BY_ADMIN_SWITCH_SUMMARY =
+ PREFIX + "ENABLED_BY_ADMIN_SWITCH_SUMMARY";
}
/**
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 4d56c1dcaa80..907db7df68d5 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -513,10 +513,23 @@ public abstract class Context {
* restart. There is no guarantee this will be respected, as the system
* tries to balance such requests from one app vs. the importance of
* keeping other apps around.
+ *
+ * @deprecated Repurposed to {@link #BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE}.
*/
+ @Deprecated
public static final int BIND_VISIBLE = 0x10000000;
/**
+ * @hide Flag for {@link #bindService}: Treat the binding as hosting a foreground service
+ * and also visible to the user. That is, the app hosting the service will get its process state
+ * bumped to the {@link android.app.ActivityManager#PROCESS_STATE_FOREGROUND_SERVICE},
+ * and it's considered as visible to the user, thus less likely to be expunged from memory
+ * on low memory situations. This is intented for use by processes with the process state
+ * better than the {@link android.app.ActivityManager#PROCESS_STATE_TOP}.
+ */
+ public static final int BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE = 0x10000000;
+
+ /**
* @hide
* Flag for {@link #bindService}: Consider this binding to be causing the target
* process to be showing UI, so it will be do a UI_HIDDEN memory trim when it goes
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 2961b5505794..24c383692c09 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -2752,4 +2752,21 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
mKnownActivityEmbeddingCerts.add(knownCert.toUpperCase(Locale.US));
}
}
+
+ /**
+ * Sets whether the application will use the {@link android.window.OnBackInvokedCallback}
+ * navigation system instead of the {@link android.view.KeyEvent#KEYCODE_BACK} and related
+ * callbacks. Intended to be used from tests only.
+ *
+ * @see #isOnBackInvokedCallbackEnabled()
+ * @hide
+ */
+ @TestApi
+ public void setEnableOnBackInvokedCallback(boolean isEnable) {
+ if (isEnable) {
+ privateFlagsExt |= PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK;
+ } else {
+ privateFlagsExt &= ~PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK;
+ }
+ }
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 44dc28d2b0fa..52e64e80e6d4 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -2618,6 +2618,15 @@ public class PackageParser {
return Build.VERSION_CODES.CUR_DEVELOPMENT;
}
+ // STOPSHIP: hack for the pre-release SDK
+ if (platformSdkCodenames.length == 0
+ && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals(
+ targetCode)) {
+ Slog.w(TAG, "Package requires development platform " + targetCode
+ + ", returning current version " + Build.VERSION.SDK_INT);
+ return Build.VERSION.SDK_INT;
+ }
+
// Otherwise, we're looking at an incompatible pre-release SDK.
if (platformSdkCodenames.length > 0) {
outError[0] = "Requires development platform " + targetCode
@@ -2689,6 +2698,15 @@ public class PackageParser {
return Build.VERSION_CODES.CUR_DEVELOPMENT;
}
+ // STOPSHIP: hack for the pre-release SDK
+ if (platformSdkCodenames.length == 0
+ && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals(
+ minCode)) {
+ Slog.w(TAG, "Package requires min development platform " + minCode
+ + ", returning current version " + Build.VERSION.SDK_INT);
+ return Build.VERSION.SDK_INT;
+ }
+
// Otherwise, we're looking at an incompatible pre-release SDK.
if (platformSdkCodenames.length > 0) {
outError[0] = "Requires development platform " + minCode
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 76e9fcb07f22..d6e13ac90f82 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -18,6 +18,7 @@ package android.content.pm;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.TestApi;
import android.annotation.UserIdInt;
import android.compat.annotation.UnsupportedAppUsage;
@@ -170,7 +171,7 @@ public class UserInfo implements Parcelable {
@UnsupportedAppUsage
public int serialNumber;
@UnsupportedAppUsage
- public String name;
+ public @Nullable String name;
@UnsupportedAppUsage
public String iconPath;
@UnsupportedAppUsage
diff --git a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
index 6d74b819301d..bde71bb90bf7 100644
--- a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
@@ -315,6 +315,15 @@ public class FrameworkParsingPackageUtils {
return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT);
}
+ // STOPSHIP: hack for the pre-release SDK
+ if (platformSdkCodenames.length == 0
+ && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals(
+ minCode)) {
+ Slog.w(TAG, "Parsed package requires min development platform " + minCode
+ + ", returning current version " + Build.VERSION.SDK_INT);
+ return input.success(Build.VERSION.SDK_INT);
+ }
+
// Otherwise, we're looking at an incompatible pre-release SDK.
if (platformSdkCodenames.length > 0) {
return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK,
@@ -367,16 +376,29 @@ public class FrameworkParsingPackageUtils {
return input.success(targetVers);
}
- if (allowUnknownCodenames && UnboundedSdkLevel.isAtMost(targetCode)) {
- return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT);
- }
-
// If it's a pre-release SDK and the codename matches this platform, it
// definitely targets this SDK.
if (matchTargetCode(platformSdkCodenames, targetCode)) {
return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT);
}
+ // STOPSHIP: hack for the pre-release SDK
+ if (platformSdkCodenames.length == 0
+ && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals(
+ targetCode)) {
+ Slog.w(TAG, "Parsed package requires development platform " + targetCode
+ + ", returning current version " + Build.VERSION.SDK_INT);
+ return input.success(Build.VERSION.SDK_INT);
+ }
+
+ try {
+ if (allowUnknownCodenames && UnboundedSdkLevel.isAtMost(targetCode)) {
+ return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT);
+ }
+ } catch (IllegalArgumentException e) {
+ return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, "Bad package SDK");
+ }
+
// Otherwise, we're looking at an incompatible pre-release SDK.
if (platformSdkCodenames.length > 0) {
return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK,
diff --git a/core/java/android/hardware/ISensorPrivacyManager.aidl b/core/java/android/hardware/ISensorPrivacyManager.aidl
index a392afdacbcb..9cf329ca3d3d 100644
--- a/core/java/android/hardware/ISensorPrivacyManager.aidl
+++ b/core/java/android/hardware/ISensorPrivacyManager.aidl
@@ -50,5 +50,7 @@ interface ISensorPrivacyManager {
void suppressToggleSensorPrivacyReminders(int userId, int sensor, IBinder token,
boolean suppress);
+ boolean requiresAuthentication();
+
void showSensorUseDialog(int sensor);
} \ No newline at end of file
diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java
index 0460e5831e33..99b58c97dc93 100644
--- a/core/java/android/hardware/SensorPrivacyManager.java
+++ b/core/java/android/hardware/SensorPrivacyManager.java
@@ -327,6 +327,8 @@ public final class SensorPrivacyManager {
@NonNull
private boolean mToggleListenerRegistered = false;
+ private Boolean mRequiresAuthentication = null;
+
/**
* Private constructor to ensure only a single instance is created.
*/
@@ -761,6 +763,23 @@ public final class SensorPrivacyManager {
}
/**
+ * @return whether the device is required to be unlocked to change software state.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
+ public boolean requiresAuthentication() {
+ if (mRequiresAuthentication == null) {
+ try {
+ mRequiresAuthentication = mService.requiresAuthentication();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return mRequiresAuthentication;
+ }
+
+ /**
* If sensor privacy for the provided sensor is enabled then this call will show the user the
* dialog which is shown when an application attempts to use that sensor. If privacy isn't
* enabled then this does nothing.
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index b05e6d131957..a90eb88bc109 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -1058,7 +1058,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* must select one unique size from this metadata to use (e.g., preview and recording streams
* must have the same size). Otherwise, the high speed capture session creation will fail.</p>
* <p>The min and max fps will be multiple times of 30fps.</p>
- * <p>High speed video streaming extends significant performance pressue to camera hardware,
+ * <p>High speed video streaming extends significant performance pressure to camera hardware,
* to achieve efficient high speed streaming, the camera device may have to aggregate
* multiple frames together and send to camera device for processing where the request
* controls are same for all the frames in this batch. Max batch size indicates
@@ -1143,7 +1143,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* <p>Range of boosts for {@link CaptureRequest#CONTROL_POST_RAW_SENSITIVITY_BOOST android.control.postRawSensitivityBoost} supported
* by this camera device.</p>
* <p>Devices support post RAW sensitivity boost will advertise
- * {@link CaptureRequest#CONTROL_POST_RAW_SENSITIVITY_BOOST android.control.postRawSensitivityBoost} key for controling
+ * {@link CaptureRequest#CONTROL_POST_RAW_SENSITIVITY_BOOST android.control.postRawSensitivityBoost} key for controlling
* post RAW sensitivity boost.</p>
* <p>This key will be <code>null</code> for devices that do not support any RAW format
* outputs. For devices that do support RAW format outputs, this key will always
@@ -1323,7 +1323,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* <p>Maximum flashlight brightness level.</p>
* <p>If this value is greater than 1, then the device supports controlling the
* flashlight brightness level via
- * {android.hardware.camera2.CameraManager#turnOnTorchWithStrengthLevel}.
+ * {@link android.hardware.camera2.CameraManager#turnOnTorchWithStrengthLevel }.
* If this value is equal to 1, flashlight brightness control is not supported.
* The value for this key will be null for devices with no flash unit.</p>
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
@@ -1335,7 +1335,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
/**
* <p>Default flashlight brightness level to be set via
- * {android.hardware.camera2.CameraManager#turnOnTorchWithStrengthLevel}.</p>
+ * {@link android.hardware.camera2.CameraManager#turnOnTorchWithStrengthLevel }.</p>
* <p>If flash unit is available this will be greater than or equal to 1 and less
* or equal to <code>{@link CameraCharacteristics#FLASH_INFO_STRENGTH_MAXIMUM_LEVEL android.flash.info.strengthMaximumLevel}</code>.</p>
* <p>Setting flashlight brightness above the default level
@@ -1376,7 +1376,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* camera device.</p>
* <p>This list will include at least one non-zero resolution, plus <code>(0,0)</code> for indicating no
* thumbnail should be generated.</p>
- * <p>Below condiditions will be satisfied for this size list:</p>
+ * <p>Below conditions will be satisfied for this size list:</p>
* <ul>
* <li>The sizes will be sorted by increasing pixel area (width x height).
* If several resolutions have the same area, they will be sorted by increasing width.</li>
@@ -1982,7 +1982,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* the camera device. Using more streams simultaneously may require more hardware and
* CPU resources that will consume more power. The image format for an output stream can
* be any supported format provided by android.scaler.availableStreamConfigurations.
- * The formats defined in android.scaler.availableStreamConfigurations can be catergorized
+ * The formats defined in android.scaler.availableStreamConfigurations can be categorized
* into the 3 stream types as below:</p>
* <ul>
* <li>Processed (but stalling): any non-RAW format with a stallDurations &gt; 0.
@@ -2324,7 +2324,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* but clients should be aware and expect delays during their application.
* An example usage scenario could look like this:</p>
* <ul>
- * <li>The camera client starts by quering the session parameter key list via
+ * <li>The camera client starts by querying the session parameter key list via
* {@link android.hardware.camera2.CameraCharacteristics#getAvailableSessionKeys }.</li>
* <li>Before triggering the capture session create sequence, a capture request
* must be built via
@@ -2379,7 +2379,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* {@link android.hardware.camera2.CameraCharacteristics#getKeys } that require camera clients
* to acquire the {@link android.Manifest.permission#CAMERA } permission before calling
* {@link android.hardware.camera2.CameraManager#getCameraCharacteristics }. If the
- * permission is not held by the camera client, then the values of the repsective properties
+ * permission is not held by the camera client, then the values of the respective properties
* will not be present in {@link android.hardware.camera2.CameraCharacteristics }.</p>
* <p>This key is available on all devices.</p>
* @hide
@@ -2759,7 +2759,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* </table>
* <p>For applications targeting SDK version 31 or newer, if the mobile device declares to be
* media performance class 12 or higher by setting
- * {@link android.os.Build.VERSION_CDOES.MEDIA_PERFORMANCE_CLASS } to be 31 or larger,
+ * {@link android.os.Build.VERSION_CODES.MEDIA_PERFORMANCE_CLASS } to be 31 or larger,
* the primary camera devices (first rear/front camera in the camera ID list) will not
* support JPEG sizes smaller than 1080p. If the application configures a JPEG stream
* smaller than 1080p, the camera device will round up the JPEG image size to at least
@@ -2833,7 +2833,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* </table>
* <p>For applications targeting SDK version 31 or newer, if the mobile device doesn't declare
* to be media performance class 12 or better by setting
- * {@link android.os.Build.VERSION_CDOES.MEDIA_PERFORMANCE_CLASS } to be 31 or larger,
+ * {@link android.os.Build.VERSION_CODES.MEDIA_PERFORMANCE_CLASS } to be 31 or larger,
* or if the camera device isn't a primary rear/front camera, the minimum required output
* stream configurations are the same as for applications targeting SDK version older than
* 31.</p>
@@ -3485,14 +3485,16 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* to output different resolution images depending on the current active physical camera or
* pixel mode. With multi-resolution input streams, the camera device can reprocess images
* of different resolutions from different physical cameras or sensor pixel modes.</p>
- * <p>When set to TRUE:
- * * For a logical multi-camera, the camera framework derives
+ * <p>When set to TRUE:</p>
+ * <ul>
+ * <li>For a logical multi-camera, the camera framework derives
* {@link CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP android.scaler.multiResolutionStreamConfigurationMap} by combining the
* android.scaler.physicalCameraMultiResolutionStreamConfigurations from its physical
- * cameras.
- * * For an ultra-high resolution sensor camera, the camera framework directly copies
+ * cameras.</li>
+ * <li>For an ultra-high resolution sensor camera, the camera framework directly copies
* the value of android.scaler.physicalCameraMultiResolutionStreamConfigurations to
- * {@link CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP android.scaler.multiResolutionStreamConfigurationMap}.</p>
+ * {@link CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP android.scaler.multiResolutionStreamConfigurationMap}.</li>
+ * </ul>
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
* <p><b>Limited capability</b> -
* Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
@@ -3513,7 +3515,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* capture, video record for encoding the camera output for the purpose of future playback,
* and video call for live realtime video conferencing.</p>
* <p>With this flag, the camera device can optimize the image processing pipeline
- * parameters, such as tuning, sensor mode, and ISP settings, indepedent of
+ * parameters, such as tuning, sensor mode, and ISP settings, independent of
* the properties of the immediate camera output surface. For example, if the output
* surface is a SurfaceTexture, the stream use case flag can be used to indicate whether
* the camera frames eventually go to display, video encoder,
@@ -3535,7 +3537,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE }
* capability is documented in the camera device
* {@link android.hardware.camera2.CameraDevice#createCaptureSession guideline}. The
- * application is strongly recommended to use one of the guaranteed stream combintations.
+ * application is strongly recommended to use one of the guaranteed stream combinations.
* If the application creates a session with a stream combination not in the guaranteed
* list, or with mixed DEFAULT and non-DEFAULT use cases within the same session,
* the camera device may ignore some stream use cases due to hardware constraints
@@ -5209,7 +5211,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* EXTERIOR_* value.</p>
* <p>If a camera has INTERIOR_OTHER or EXTERIOR_OTHER, or more than one camera is at the
* same location and facing the same direction, their static metadata will list the
- * following entries, so that applications can determain their lenses' exact facing
+ * following entries, so that applications can determine their lenses' exact facing
* directions:</p>
* <ul>
* <li>{@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference}</li>
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 40565b06a8f2..eb8c73aced39 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -409,7 +409,7 @@ public abstract class CameraMetadata<TKey> {
/**
* <p>The value of {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation} is relative to the origin of the
- * automotive sensor coodinate system, which is at the center of the rear axle.</p>
+ * automotive sensor coordinate system, which is at the center of the rear axle.</p>
*
* @see CameraCharacteristics#LENS_POSE_TRANSLATION
* @see CameraCharacteristics#LENS_POSE_REFERENCE
@@ -683,7 +683,7 @@ public abstract class CameraMetadata<TKey> {
* captured at the same rate as the maximum-size YUV_420_888 resolution is.</p>
* <p>If the device supports the PRIVATE_REPROCESSING capability, then the same guarantees
* as for the YUV_420_888 format also apply to the {@link android.graphics.ImageFormat#PRIVATE } format.</p>
- * <p>In addition, the {@link CameraCharacteristics#SYNC_MAX_LATENCY android.sync.maxLatency} field is guaranted to have a value between 0
+ * <p>In addition, the {@link CameraCharacteristics#SYNC_MAX_LATENCY android.sync.maxLatency} field is guaranteed to have a value between 0
* and 4, inclusive. {@link CameraCharacteristics#CONTROL_AE_LOCK_AVAILABLE android.control.aeLockAvailable} and {@link CameraCharacteristics#CONTROL_AWB_LOCK_AVAILABLE android.control.awbLockAvailable}
* are also guaranteed to be <code>true</code> so burst capture with these two locks ON yields
* consistent image output.</p>
@@ -843,7 +843,7 @@ public abstract class CameraMetadata<TKey> {
* {@link android.hardware.camera2.params.StreamConfigurationMap#getHighSpeedVideoSizes }.</li>
* <li>The FPS ranges are selected from {@link android.hardware.camera2.params.StreamConfigurationMap#getHighSpeedVideoFpsRanges }.</li>
* </ul>
- * <p>When above conditions are NOT satistied,
+ * <p>When above conditions are NOT satisfied,
* {@link android.hardware.camera2.CameraDevice#createConstrainedHighSpeedCaptureSession }
* will fail.</p>
* <p>Switching to a FPS range that has different maximum FPS may trigger some camera device
@@ -986,7 +986,7 @@ public abstract class CameraMetadata<TKey> {
* non-active physical cameras. For example, if the logical camera has a wide-ultrawide
* configuration where the wide lens is the default, when the crop region is set to the
* logical camera's active array size, (and the zoom ratio set to 1.0 starting from
- * Android 11), a physical stream for the ultrawide camera may prefer outputing images
+ * Android 11), a physical stream for the ultrawide camera may prefer outputting images
* with larger field-of-view than that of the wide camera for better stereo matching
* margin or more robust motion tracking. At the same time, the physical non-RAW streams'
* field of view must not be smaller than the requested crop region and zoom ratio, as
@@ -1175,21 +1175,23 @@ public abstract class CameraMetadata<TKey> {
* when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
* {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }),
* the <code>RAW_SENSOR</code> stream will have a regular bayer pattern.</p>
- * <p>This capability requires the camera device to support the following :
- * * The {@link android.hardware.camera2.params.StreamConfigurationMap } mentioned below
+ * <p>This capability requires the camera device to support the following :</p>
+ * <ul>
+ * <li>The {@link android.hardware.camera2.params.StreamConfigurationMap } mentioned below
* refers to the one, described by
- * <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION android.scaler.streamConfigurationMapMaximumResolution}</code>.
- * * One input stream is supported, that is, <code>{@link CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS android.request.maxNumInputStreams} == 1</code>.
- * * {@link android.graphics.ImageFormat#RAW_SENSOR } is supported as an output/input
+ * <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION android.scaler.streamConfigurationMapMaximumResolution}</code>.</li>
+ * <li>One input stream is supported, that is, <code>{@link CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS android.request.maxNumInputStreams} == 1</code>.</li>
+ * <li>{@link android.graphics.ImageFormat#RAW_SENSOR } is supported as an output/input
* format, that is, {@link android.graphics.ImageFormat#RAW_SENSOR } is included in the
- * lists of formats returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats } and {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputFormats }.
- * * {@link android.hardware.camera2.params.StreamConfigurationMap#getValidOutputFormatsForInput }
- * returns non-empty int[] for each supported input format returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats }.
- * * Each size returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputSizes getInputSizes(ImageFormat.RAW_SENSOR)} is also included in {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputSizes getOutputSizes(ImageFormat.RAW_SENSOR)}
- * * Using {@link android.graphics.ImageFormat#RAW_SENSOR } does not cause a frame rate
- * drop relative to the sensor's maximum capture rate (at that resolution).
- * * No CaptureRequest controls will be applicable when a request has an input target
- * with {@link android.graphics.ImageFormat#RAW_SENSOR } format.</p>
+ * lists of formats returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats } and {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputFormats }.</li>
+ * <li>{@link android.hardware.camera2.params.StreamConfigurationMap#getValidOutputFormatsForInput }
+ * returns non-empty int[] for each supported input format returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats }.</li>
+ * <li>Each size returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputSizes getInputSizes(ImageFormat.RAW_SENSOR)} is also included in {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputSizes getOutputSizes(ImageFormat.RAW_SENSOR)}</li>
+ * <li>Using {@link android.graphics.ImageFormat#RAW_SENSOR } does not cause a frame rate
+ * drop relative to the sensor's maximum capture rate (at that resolution).</li>
+ * <li>No CaptureRequest controls will be applicable when a request has an input target
+ * with {@link android.graphics.ImageFormat#RAW_SENSOR } format.</li>
+ * </ul>
*
* @see CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS
* @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION
@@ -1205,16 +1207,18 @@ public abstract class CameraMetadata<TKey> {
* {@link android.hardware.camera2.params.DynamicRangeProfiles#getSupportedProfiles }.
* They can be configured as part of the capture session initialization via
* {@link android.hardware.camera2.params.OutputConfiguration#setDynamicRangeProfile }.
- * Cameras that enable this capability must also support the following:
- * * Profile {@link android.hardware.camera2.params.DynamicRangeProfiles#HLG10 }
- * * All mandatory stream combinations for this specific capability as per
- * documentation {@link android.hardware.camera2.CameraDevice#createCaptureSession }
- * * In case the device is not able to capture some combination of supported
+ * Cameras that enable this capability must also support the following:</p>
+ * <ul>
+ * <li>Profile {@link android.hardware.camera2.params.DynamicRangeProfiles#HLG10 }</li>
+ * <li>All mandatory stream combinations for this specific capability as per
+ * documentation {@link android.hardware.camera2.CameraDevice#createCaptureSession }</li>
+ * <li>In case the device is not able to capture some combination of supported
* standard 8-bit and/or 10-bit dynamic range profiles within the same capture request,
* then those constraints must be listed in
- * {@link android.hardware.camera2.params.DynamicRangeProfiles#getProfileCaptureRequestConstraints }
- * * Recommended dynamic range profile listed in
- * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_RECOMMENDED_TEN_BIT_DYNAMIC_RANGE_PROFILE }.</p>
+ * {@link android.hardware.camera2.params.DynamicRangeProfiles#getProfileCaptureRequestConstraints }</li>
+ * <li>Recommended dynamic range profile listed in
+ * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_RECOMMENDED_TEN_BIT_DYNAMIC_RANGE_PROFILE }.</li>
+ * </ul>
* @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
*/
public static final int REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT = 18;
@@ -1224,22 +1228,26 @@ public abstract class CameraMetadata<TKey> {
* {@link android.hardware.camera2.params.OutputConfiguration#setStreamUseCase }
* so that the device can optimize camera pipeline parameters such as tuning, sensor
* mode, or ISP settings for a specific user scenario.
- * Some sample usages of this capability are:
- * * Distinguish high quality YUV captures from a regular YUV stream where
- * the image quality may not be as good as the JPEG stream, or
- * * Use one stream to serve multiple purposes: viewfinder, video recording and
+ * Some sample usages of this capability are:</p>
+ * <ul>
+ * <li>Distinguish high quality YUV captures from a regular YUV stream where
+ * the image quality may not be as good as the JPEG stream, or</li>
+ * <li>Use one stream to serve multiple purposes: viewfinder, video recording and
* still capture. This is common with applications that wish to apply edits equally
- * to preview, saved images, and saved videos.</p>
+ * to preview, saved images, and saved videos.</li>
+ * </ul>
* <p>This capability requires the camera device to support the following
- * stream use cases:
- * * DEFAULT for backward compatibility where the application doesn't set
- * a stream use case
- * * PREVIEW for live viewfinder and in-app image analysis
- * * STILL_CAPTURE for still photo capture
- * * VIDEO_RECORD for recording video clips
- * * PREVIEW_VIDEO_STILL for one single stream used for viewfinder, video
- * recording, and still capture.
- * * VIDEO_CALL for long running video calls</p>
+ * stream use cases:</p>
+ * <ul>
+ * <li>DEFAULT for backward compatibility where the application doesn't set
+ * a stream use case</li>
+ * <li>PREVIEW for live viewfinder and in-app image analysis</li>
+ * <li>STILL_CAPTURE for still photo capture</li>
+ * <li>VIDEO_RECORD for recording video clips</li>
+ * <li>PREVIEW_VIDEO_STILL for one single stream used for viewfinder, video
+ * recording, and still capture.</li>
+ * <li>VIDEO_CALL for long running video calls</li>
+ * </ul>
* <p>{@link android.hardware.camera2.CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES }
* lists all of the supported stream use cases.</p>
* <p>Refer to {@link android.hardware.camera2.CameraDevice#createCaptureSession } for the
@@ -1391,10 +1399,10 @@ public abstract class CameraMetadata<TKey> {
* <p>Live stream shown to the user.</p>
* <p>Optimized for performance and usability as a viewfinder, but not necessarily for
* image quality. The output is not meant to be persisted as saved images or video.</p>
- * <p>No stall if android.control.<em> are set to FAST; may have stall if android.control.</em>
- * are set to HIGH_QUALITY. This use case has the same behavior as the default
- * SurfaceView and SurfaceTexture targets. Additionally, this use case can be used for
- * in-app image analysis.</p>
+ * <p>No stall if android.control.* are set to FAST. There may be stall if
+ * they are set to HIGH_QUALITY. This use case has the same behavior as the
+ * default SurfaceView and SurfaceTexture targets. Additionally, this use case can be
+ * used for in-app image analysis.</p>
* @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES
*/
public static final int SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW = 0x1;
@@ -1441,7 +1449,7 @@ public abstract class CameraMetadata<TKey> {
public static final int SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL = 0x4;
/**
- * <p>Long-running video call optimized for both power efficienty and video quality.</p>
+ * <p>Long-running video call optimized for both power efficiency and video quality.</p>
* <p>The camera sensor may run in a lower-resolution mode to reduce power consumption
* at the cost of some image and digital zoom quality. Unlike VIDEO_RECORD, VIDEO_CALL
* outputs are expected to work in dark conditions, so are usually accompanied with
@@ -2946,10 +2954,10 @@ public abstract class CameraMetadata<TKey> {
* android.control.availableHighSpeedVideoConfigurations.</li>
* <li>No processed non-stalling or raw streams are configured.</li>
* </ul>
- * <p>When above conditions are NOT satistied, the controls of this mode and
+ * <p>When above conditions are NOT satisfied, the controls of this mode and
* {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE android.control.aeTargetFpsRange} will be ignored by the camera device,
* the camera device will fall back to {@link CaptureRequest#CONTROL_MODE android.control.mode} <code>==</code> AUTO,
- * and the returned capture result metadata will give the fps range choosen
+ * and the returned capture result metadata will give the fps range chosen
* by the camera device.</p>
* <p>Switching into or out of this mode may trigger some camera ISP/sensor
* reconfigurations, which may introduce extra latency. It is recommended that
@@ -3034,7 +3042,7 @@ public abstract class CameraMetadata<TKey> {
* if the {@link CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE android.sensor.info.sensitivityRange} gives range of [100, 1600],
* the camera device auto-exposure routine tuning process may limit the actual
* exposure sensitivity range to [100, 1200] to ensure that the noise level isn't
- * exessive in order to preserve the image quality. Under this situation, the image under
+ * excessive in order to preserve the image quality. Under this situation, the image under
* low light may be under-exposed when the sensor max exposure time (bounded by the
* {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE android.control.aeTargetFpsRange} when {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is one of the
* ON_* modes) and effective max sensitivity are reached. This scene mode allows the
@@ -3631,7 +3639,7 @@ public abstract class CameraMetadata<TKey> {
public static final int TONEMAP_MODE_HIGH_QUALITY = 2;
/**
- * <p>Use the gamma value specified in {@link CaptureRequest#TONEMAP_GAMMA android.tonemap.gamma} to peform
+ * <p>Use the gamma value specified in {@link CaptureRequest#TONEMAP_GAMMA android.tonemap.gamma} to perform
* tonemapping.</p>
* <p>All color enhancement and tonemapping must be disabled, except
* for applying the tonemapping curve specified by {@link CaptureRequest#TONEMAP_GAMMA android.tonemap.gamma}.</p>
@@ -3644,7 +3652,7 @@ public abstract class CameraMetadata<TKey> {
/**
* <p>Use the preset tonemapping curve specified in
- * {@link CaptureRequest#TONEMAP_PRESET_CURVE android.tonemap.presetCurve} to peform tonemapping.</p>
+ * {@link CaptureRequest#TONEMAP_PRESET_CURVE android.tonemap.presetCurve} to perform tonemapping.</p>
* <p>All color enhancement and tonemapping must be disabled, except
* for applying the tonemapping curve specified by
* {@link CaptureRequest#TONEMAP_PRESET_CURVE android.tonemap.presetCurve}.</p>
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 15e59e03ee70..c5cf0f695040 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -1767,7 +1767,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* routine is enabled, overriding the application's selected
* {@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform}, {@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains} and
* {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode}. Note that when {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode}
- * is OFF, the behavior of AWB is device dependent. It is recommened to
+ * is OFF, the behavior of AWB is device dependent. It is recommended to
* also set AWB mode to OFF or lock AWB by using {@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} before
* setting AE mode to OFF.</p>
* <p>When set to the OFF mode, the camera device's auto-white balance
@@ -1917,13 +1917,15 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* strategy.</p>
* <p>This control (except for MANUAL) is only effective if
* <code>{@link CaptureRequest#CONTROL_MODE android.control.mode} != OFF</code> and any 3A routine is active.</p>
- * <p>All intents are supported by all devices, except that:
- * * ZERO_SHUTTER_LAG will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
- * PRIVATE_REPROCESSING or YUV_REPROCESSING.
- * * MANUAL will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
- * MANUAL_SENSOR.
- * * MOTION_TRACKING will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
- * MOTION_TRACKING.</p>
+ * <p>All intents are supported by all devices, except that:</p>
+ * <ul>
+ * <li>ZERO_SHUTTER_LAG will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
+ * PRIVATE_REPROCESSING or YUV_REPROCESSING.</li>
+ * <li>MANUAL will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
+ * MANUAL_SENSOR.</li>
+ * <li>MOTION_TRACKING will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
+ * MOTION_TRACKING.</li>
+ * </ul>
* <p><b>Possible values:</b></p>
* <ul>
* <li>{@link #CONTROL_CAPTURE_INTENT_CUSTOM CUSTOM}</li>
@@ -2680,7 +2682,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* and keep jpeg and thumbnail image data unrotated.</li>
* <li>Rotate the jpeg and thumbnail image data and not set
* {@link android.media.ExifInterface#TAG_ORIENTATION EXIF orientation flag}. In this
- * case, LIMITED or FULL hardware level devices will report rotated thumnail size in
+ * case, LIMITED or FULL hardware level devices will report rotated thumbnail size in
* capture result, so the width and height will be interchanged if 90 or 270 degree
* orientation is requested. LEGACY device will always report unrotated thumbnail
* size.</li>
@@ -3806,9 +3808,11 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
/**
* <p>Tonemapping curve to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is
* GAMMA_VALUE</p>
- * <p>The tonemap curve will be defined the following formula:
- * * OUT = pow(IN, 1.0 / gamma)
- * where IN and OUT is the input pixel value scaled to range [0.0, 1.0],
+ * <p>The tonemap curve will be defined the following formula:</p>
+ * <ul>
+ * <li>OUT = pow(IN, 1.0 / gamma)</li>
+ * </ul>
+ * <p>where IN and OUT is the input pixel value scaled to range [0.0, 1.0],
* pow is the power function and gamma is the gamma value specified by this
* key.</p>
* <p>The same curve will be applied to all color channels. The camera device
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 1faec5b76524..3e1deb27584e 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -1173,7 +1173,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <td align="center">Any state (excluding LOCKED)</td>
* <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is CANCEL, converged</td>
* <td align="center">CONVERGED</td>
- * <td align="center">Converged after a precapture sequenceis canceled, transient states are skipped by camera device.</td>
+ * <td align="center">Converged after a precapture sequences canceled, transient states are skipped by camera device.</td>
* </tr>
* <tr>
* <td align="center">CONVERGED</td>
@@ -1847,7 +1847,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* routine is enabled, overriding the application's selected
* {@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform}, {@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains} and
* {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode}. Note that when {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode}
- * is OFF, the behavior of AWB is device dependent. It is recommened to
+ * is OFF, the behavior of AWB is device dependent. It is recommended to
* also set AWB mode to OFF or lock AWB by using {@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} before
* setting AE mode to OFF.</p>
* <p>When set to the OFF mode, the camera device's auto-white balance
@@ -1997,13 +1997,15 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* strategy.</p>
* <p>This control (except for MANUAL) is only effective if
* <code>{@link CaptureRequest#CONTROL_MODE android.control.mode} != OFF</code> and any 3A routine is active.</p>
- * <p>All intents are supported by all devices, except that:
- * * ZERO_SHUTTER_LAG will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
- * PRIVATE_REPROCESSING or YUV_REPROCESSING.
- * * MANUAL will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
- * MANUAL_SENSOR.
- * * MOTION_TRACKING will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
- * MOTION_TRACKING.</p>
+ * <p>All intents are supported by all devices, except that:</p>
+ * <ul>
+ * <li>ZERO_SHUTTER_LAG will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
+ * PRIVATE_REPROCESSING or YUV_REPROCESSING.</li>
+ * <li>MANUAL will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
+ * MANUAL_SENSOR.</li>
+ * <li>MOTION_TRACKING will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains
+ * MOTION_TRACKING.</li>
+ * </ul>
* <p><b>Possible values:</b></p>
* <ul>
* <li>{@link #CONTROL_CAPTURE_INTENT_CUSTOM CUSTOM}</li>
@@ -2929,7 +2931,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* and keep jpeg and thumbnail image data unrotated.</li>
* <li>Rotate the jpeg and thumbnail image data and not set
* {@link android.media.ExifInterface#TAG_ORIENTATION EXIF orientation flag}. In this
- * case, LIMITED or FULL hardware level devices will report rotated thumnail size in
+ * case, LIMITED or FULL hardware level devices will report rotated thumbnail size in
* capture result, so the width and height will be interchanged if 90 or 270 degree
* orientation is requested. LEGACY device will always report unrotated thumbnail
* size.</li>
@@ -3149,7 +3151,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <p>When the state is STATIONARY, the lens parameters are not changing. This could be
* either because the parameters are all fixed, or because the lens has had enough
* time to reach the most recently-requested values.
- * If all these lens parameters are not changable for a camera device, as listed below:</p>
+ * If all these lens parameters are not changeable for a camera device, as listed below:</p>
* <ul>
* <li>Fixed focus (<code>{@link CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE android.lens.info.minimumFocusDistance} == 0</code>), which means
* {@link CaptureRequest#LENS_FOCUS_DISTANCE android.lens.focusDistance} parameter will always be 0.</li>
@@ -4009,7 +4011,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* noise model used here is:</p>
* <p>N(x) = sqrt(Sx + O)</p>
* <p>Where x represents the recorded signal of a CFA channel normalized to
- * the range [0, 1], and S and O are the noise model coeffiecients for
+ * the range [0, 1], and S and O are the noise model coefficients for
* that channel.</p>
* <p>A more detailed description of the noise model can be found in the
* Adobe DNG specification for the NoiseProfile tag.</p>
@@ -4054,7 +4056,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <li>1.20 &lt;= R &gt;= 1.03 will require some software
* correction to avoid demosaic errors (3-20% divergence).</li>
* <li>R &gt; 1.20 will require strong software correction to produce
- * a usuable image (&gt;20% divergence).</li>
+ * a usable image (&gt;20% divergence).</li>
* </ul>
* <p>Starting from Android Q, this key will not be present for a MONOCHROME camera, even if
* the camera device has RAW capability.</p>
@@ -4274,7 +4276,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
/**
* <p>Whether <code>RAW</code> images requested have their bayer pattern as described by
* {@link CameraCharacteristics#SENSOR_INFO_BINNING_FACTOR android.sensor.info.binningFactor}.</p>
- * <p>This key will only be present in devices advertisting the
+ * <p>This key will only be present in devices advertising the
* {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR }
* capability which also advertise <code>REMOSAIC_REPROCESSING</code> capability. On all other devices
* RAW targets will have a regular bayer pattern.</p>
@@ -5128,9 +5130,11 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
/**
* <p>Tonemapping curve to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is
* GAMMA_VALUE</p>
- * <p>The tonemap curve will be defined the following formula:
- * * OUT = pow(IN, 1.0 / gamma)
- * where IN and OUT is the input pixel value scaled to range [0.0, 1.0],
+ * <p>The tonemap curve will be defined the following formula:</p>
+ * <ul>
+ * <li>OUT = pow(IN, 1.0 / gamma)</li>
+ * </ul>
+ * <p>where IN and OUT is the input pixel value scaled to range [0.0, 1.0],
* pow is the power function and gamma is the gamma value specified by this
* key.</p>
* <p>The same curve will be applied to all color channels. The camera device
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 1263da6e4884..4d0ba63d7759 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -72,7 +72,7 @@ import java.util.Set;
import java.util.concurrent.Executor;
public final class CameraExtensionSessionImpl extends CameraExtensionSession {
- private static final int PREVIEW_QUEUE_SIZE = 3;
+ private static final int PREVIEW_QUEUE_SIZE = 10;
private static final String TAG = "CameraExtensionSessionImpl";
private final Executor mExecutor;
@@ -1057,15 +1057,8 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
mClientRequest));
if (mCaptureResultHandler != null) {
- CameraMetadataNative captureResults = new CameraMetadataNative();
- for (CaptureResult.Key key : mSupportedResultKeys) {
- Object value = result.get(key);
- if (value != null) {
- captureResults.set(key, value);
- }
- }
mCaptureResultHandler.onCaptureCompleted(timestamp,
- captureResults);
+ initializeFilteredResults(result));
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -1127,6 +1120,11 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
private class ImageCallback implements OnImageAvailableListener {
@Override
+ public void onImageDropped(long timestamp) {
+ notifyCaptureFailed();
+ }
+
+ @Override
public void onImageAvailable(ImageReader reader, Image img) {
if (mCaptureFailed) {
img.close();
@@ -1160,6 +1158,9 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
private class ImageLoopbackCallback implements OnImageAvailableListener {
@Override
+ public void onImageDropped(long timestamp) { }
+
+ @Override
public void onImageAvailable(ImageReader reader, Image img) {
img.close();
}
@@ -1221,7 +1222,8 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
}
private interface OnImageAvailableListener {
- public void onImageAvailable (ImageReader reader, Image img);
+ void onImageDropped(long timestamp);
+ void onImageAvailable (ImageReader reader, Image img);
}
private class CameraOutputImageCallback implements ImageReader.OnImageAvailableListener,
@@ -1263,6 +1265,29 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
} else {
mImageListenerMap.put(img.getTimestamp(), new Pair<>(img, null));
}
+
+ notifyDroppedImages(timestamp);
+ }
+
+ private void notifyDroppedImages(long timestamp) {
+ Set<Long> timestamps = mImageListenerMap.keySet();
+ ArrayList<Long> removedTs = new ArrayList<>();
+ for (long ts : timestamps) {
+ if (ts < timestamp) {
+ Log.e(TAG, "Dropped image with ts: " + ts);
+ Pair<Image, OnImageAvailableListener> entry = mImageListenerMap.get(ts);
+ if (entry.second != null) {
+ entry.second.onImageDropped(ts);
+ }
+ if (entry.first != null) {
+ entry.first.close();
+ }
+ removedTs.add(ts);
+ }
+ }
+ for (long ts : removedTs) {
+ mImageListenerMap.remove(ts);
+ }
}
public void registerListener(Long timestamp, OnImageAvailableListener listener) {
@@ -1291,6 +1316,12 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
entry.first.close();
}
}
+ for (long timestamp : mImageListenerMap.keySet()) {
+ Pair<Image, OnImageAvailableListener> entry = mImageListenerMap.get(timestamp);
+ if (entry.second != null) {
+ entry.second.onImageDropped(timestamp);
+ }
+ }
mImageListenerMap.clear();
}
}
@@ -1447,7 +1478,6 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
} else {
notifyConfigurationFailure();
}
-
}
@Override
@@ -1536,7 +1566,16 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
} else if (mPreviewProcessorType ==
IPreviewExtenderImpl.PROCESSOR_TYPE_IMAGE_PROCESSOR) {
int idx = mPendingResultMap.indexOfKey(timestamp);
- if (idx >= 0) {
+
+ if ((idx >= 0) && (mPendingResultMap.get(timestamp).first == null)) {
+ // Image was dropped before we can receive the capture results
+ if ((mCaptureResultHandler != null)) {
+ mCaptureResultHandler.onCaptureCompleted(timestamp,
+ initializeFilteredResults(result));
+ }
+ discardPendingRepeatingResults(idx, mPendingResultMap, false);
+ } else if (idx >= 0) {
+ // Image came before the capture results
ParcelImage parcelImage = initializeParcelImage(
mPendingResultMap.get(timestamp).first);
try {
@@ -1563,6 +1602,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
}
discardPendingRepeatingResults(idx, mPendingResultMap, false);
} else {
+ // Image not yet available
notifyClient = false;
mPendingResultMap.put(timestamp,
new Pair<>(null,
@@ -1581,16 +1621,8 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
mClientRequest));
if ((mCaptureResultHandler != null) && (mPreviewProcessorType !=
IPreviewExtenderImpl.PROCESSOR_TYPE_IMAGE_PROCESSOR)) {
- CameraMetadataNative captureResults =
- new CameraMetadataNative();
- for (CaptureResult.Key key : mSupportedResultKeys) {
- Object value = result.get(key);
- if (value != null) {
- captureResults.set(key, value);
- }
- }
mCaptureResultHandler.onCaptureCompleted(timestamp,
- captureResults);
+ initializeFilteredResults(result));
}
} else {
mExecutor.execute(
@@ -1657,19 +1689,24 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
for (int i = idx; i >= 0; i--) {
if (previewMap.valueAt(i).first != null) {
previewMap.valueAt(i).first.close();
- } else {
- if (mClientNotificationsEnabled && ((i != idx) || notifyCurrentIndex)) {
- Log.w(TAG, "Preview frame drop with timestamp: " + previewMap.keyAt(i));
- final long ident = Binder.clearCallingIdentity();
- try {
- mExecutor.execute(
- () -> mCallbacks
- .onCaptureFailed(CameraExtensionSessionImpl.this,
- mClientRequest));
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
+ } else if (mClientNotificationsEnabled && (previewMap.valueAt(i).second != null) &&
+ ((i != idx) || notifyCurrentIndex)) {
+ TotalCaptureResult result = previewMap.valueAt(i).second;
+ Long timestamp = result.get(CaptureResult.SENSOR_TIMESTAMP);
+ mCaptureResultHandler.onCaptureCompleted(timestamp,
+ initializeFilteredResults(result));
+
+ Log.w(TAG, "Preview frame drop with timestamp: " + previewMap.keyAt(i));
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(
+ () -> mCallbacks
+ .onCaptureFailed(CameraExtensionSessionImpl.this,
+ mClientRequest));
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
+
}
previewMap.removeAt(i);
}
@@ -1683,6 +1720,12 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
}
@Override
+ public void onImageDropped(long timestamp) {
+ discardPendingRepeatingResults(mPendingResultMap.indexOfKey(timestamp),
+ mPendingResultMap, true);
+ }
+
+ @Override
public void onImageAvailable(ImageReader reader, Image img) {
if (img == null) {
Log.e(TAG, "Invalid image!");
@@ -1703,6 +1746,15 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
private class ImageProcessCallback implements OnImageAvailableListener {
@Override
+ public void onImageDropped(long timestamp) {
+ discardPendingRepeatingResults(mPendingResultMap.indexOfKey(timestamp),
+ mPendingResultMap, true);
+ // Add an empty frame&results entry to flag that we dropped a frame
+ // and valid capture results can immediately return to client.
+ mPendingResultMap.put(timestamp, new Pair<>(null, null));
+ }
+
+ @Override
public void onImageAvailable(ImageReader reader, Image img) {
if (mPendingResultMap.size() + 1 >= PREVIEW_QUEUE_SIZE) {
// We reached the maximum acquired images limit. This is possible in case we
@@ -1768,6 +1820,17 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
}
}
+ private CameraMetadataNative initializeFilteredResults(TotalCaptureResult result) {
+ CameraMetadataNative captureResults = new CameraMetadataNative();
+ for (CaptureResult.Key key : mSupportedResultKeys) {
+ Object value = result.get(key);
+ if (value != null) {
+ captureResults.set(key, value);
+ }
+ }
+ return captureResults;
+ }
+
private static Size findSmallestAspectMatchedSize(@NonNull List<Size> sizes,
@NonNull Size arSize) {
final float TOLL = .01f;
diff --git a/core/java/android/hardware/camera2/params/DynamicRangeProfiles.java b/core/java/android/hardware/camera2/params/DynamicRangeProfiles.java
index 14ed689af26f..34c83366e42c 100644
--- a/core/java/android/hardware/camera2/params/DynamicRangeProfiles.java
+++ b/core/java/android/hardware/camera2/params/DynamicRangeProfiles.java
@@ -277,7 +277,7 @@ public final class DynamicRangeProfiles {
* profile.</p>
*
* @return non-modifiable set of dynamic range profiles
- * @throws IllegalArgumentException - If the profile argument is not
+ * @throws IllegalArgumentException If the profile argument is not
* within the list returned by
* getSupportedProfiles()
*
@@ -303,7 +303,7 @@ public final class DynamicRangeProfiles {
*
* @return true if the given profile is not suitable for latency sensitive use cases, false
* otherwise
- * @throws IllegalArgumentException - If the profile argument is not
+ * @throws IllegalArgumentException If the profile argument is not
* within the list returned by
* getSupportedProfiles()
*
diff --git a/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java
index 4bf9a740f971..f701ec3ec367 100644
--- a/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java
+++ b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java
@@ -39,6 +39,7 @@ public class FingerprintSensorPropertiesInternal extends SensorPropertiesInterna
* See {@link FingerprintSensorProperties.SensorType}.
*/
public final @FingerprintSensorProperties.SensorType int sensorType;
+ public final boolean halControlsIllumination;
private final List<SensorLocationInternal> mSensorLocations;
@@ -46,6 +47,7 @@ public class FingerprintSensorPropertiesInternal extends SensorPropertiesInterna
@SensorProperties.Strength int strength, int maxEnrollmentsPerUser,
@NonNull List<ComponentInfoInternal> componentInfo,
@FingerprintSensorProperties.SensorType int sensorType,
+ boolean halControlsIllumination,
boolean resetLockoutRequiresHardwareAuthToken,
@NonNull List<SensorLocationInternal> sensorLocations) {
// IBiometricsFingerprint@2.1 handles lockout in the framework, so the challenge is not
@@ -55,6 +57,7 @@ public class FingerprintSensorPropertiesInternal extends SensorPropertiesInterna
super(sensorId, strength, maxEnrollmentsPerUser, componentInfo,
resetLockoutRequiresHardwareAuthToken, false /* resetLockoutRequiresChallenge */);
this.sensorType = sensorType;
+ this.halControlsIllumination = halControlsIllumination;
this.mSensorLocations = List.copyOf(sensorLocations);
}
@@ -68,14 +71,15 @@ public class FingerprintSensorPropertiesInternal extends SensorPropertiesInterna
boolean resetLockoutRequiresHardwareAuthToken) {
// TODO(b/179175438): Value should be provided from the HAL
this(sensorId, strength, maxEnrollmentsPerUser, componentInfo, sensorType,
- resetLockoutRequiresHardwareAuthToken, List.of(new SensorLocationInternal(
- "" /* displayId */, 540 /* sensorLocationX */, 1636 /* sensorLocationY */,
- 130 /* sensorRadius */)));
+ false /* halControlsIllumination */, resetLockoutRequiresHardwareAuthToken,
+ List.of(new SensorLocationInternal("" /* displayId */, 540 /* sensorLocationX */,
+ 1636 /* sensorLocationY */, 130 /* sensorRadius */)));
}
protected FingerprintSensorPropertiesInternal(Parcel in) {
super(in);
sensorType = in.readInt();
+ halControlsIllumination = in.readBoolean();
mSensorLocations = in.createTypedArrayList(SensorLocationInternal.CREATOR);
}
@@ -101,6 +105,7 @@ public class FingerprintSensorPropertiesInternal extends SensorPropertiesInterna
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(sensorType);
+ dest.writeBoolean(halControlsIllumination);
dest.writeTypedList(mSensorLocations);
}
diff --git a/core/java/android/hardware/input/InputManagerInternal.java b/core/java/android/hardware/input/InputManagerInternal.java
index b37c27c2a9e7..fc6bc555e823 100644
--- a/core/java/android/hardware/input/InputManagerInternal.java
+++ b/core/java/android/hardware/input/InputManagerInternal.java
@@ -75,8 +75,15 @@ public abstract class InputManagerInternal {
/**
* Sets the display id that the MouseCursorController will be forced to target. Pass
* {@link android.view.Display#INVALID_DISPLAY} to clear the override.
+ *
+ * Note: This method generally blocks until the pointer display override has propagated.
+ * When setting a new override, the caller should ensure that an input device that can control
+ * the mouse pointer is connected. If a new override is set when no such input device is
+ * connected, the caller may be blocked for an arbitrary period of time.
+ *
+ * @return true if the pointer displayId was set successfully, or false if it fails.
*/
- public abstract void setVirtualMousePointerDisplayId(int pointerDisplayId);
+ public abstract boolean setVirtualMousePointerDisplayId(int pointerDisplayId);
/**
* Gets the display id that the MouseCursorController is being forced to target. Returns
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index f9ed0e3db499..3f49b73a78fc 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -37,6 +37,7 @@ import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethod;
import android.view.inputmethod.InputMethodSession;
import android.view.inputmethod.InputMethodSubtype;
+import android.window.ImeOnBackInvokedDispatcher;
import com.android.internal.inputmethod.CancellationGroup;
import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
@@ -194,7 +195,9 @@ class IInputMethodWrapper extends IInputMethod.Stub
case DO_START_INPUT: {
final SomeArgs args = (SomeArgs) msg.obj;
final IBinder startInputToken = (IBinder) args.arg1;
- final IInputContext inputContext = (IInputContext) args.arg2;
+ final IInputContext inputContext = (IInputContext) ((SomeArgs) args.arg2).arg1;
+ final ImeOnBackInvokedDispatcher imeDispatcher =
+ (ImeOnBackInvokedDispatcher) ((SomeArgs) args.arg2).arg2;
final EditorInfo info = (EditorInfo) args.arg3;
final CancellationGroup cancellationGroup = (CancellationGroup) args.arg4;
final boolean restarting = args.argi5 == 1;
@@ -205,7 +208,7 @@ class IInputMethodWrapper extends IInputMethod.Stub
: null;
info.makeCompatible(mTargetSdkVersion);
inputMethod.dispatchStartInputWithToken(ic, info, restarting, startInputToken,
- navButtonFlags);
+ navButtonFlags, imeDispatcher);
args.recycle();
return;
}
@@ -348,13 +351,17 @@ class IInputMethodWrapper extends IInputMethod.Stub
@Override
public void startInput(IBinder startInputToken, IInputContext inputContext,
EditorInfo attribute, boolean restarting,
- @InputMethodNavButtonFlags int navButtonFlags) {
+ @InputMethodNavButtonFlags int navButtonFlags,
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
if (mCancellationGroup == null) {
Log.e(TAG, "startInput must be called after bindInput.");
mCancellationGroup = new CancellationGroup();
}
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = inputContext;
+ args.arg2 = imeDispatcher;
mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOII(DO_START_INPUT, startInputToken,
- inputContext, attribute, mCancellationGroup, restarting ? 1 : 0, navButtonFlags));
+ args, attribute, mCancellationGroup, restarting ? 1 : 0, navButtonFlags));
}
@BinderThread
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index efd4f0681838..25296bc0a8b9 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -101,6 +101,7 @@ import android.view.BatchedInputEventReceiver.SimpleBatchedInputEventReceiver;
import android.view.Choreographer;
import android.view.Gravity;
import android.view.InputChannel;
+import android.view.InputDevice;
import android.view.InputEventReceiver;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
@@ -134,6 +135,9 @@ import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
+import android.window.ImeOnBackInvokedDispatcher;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
import android.window.WindowMetricsHelper;
import com.android.internal.annotations.GuardedBy;
@@ -344,6 +348,9 @@ public class InputMethodService extends AbstractInputMethodService {
* A circular buffer of size MAX_EVENTS_BUFFER in case IME is taking too long to add ink view.
**/
private RingBuffer<MotionEvent> mPendingEvents;
+ private ImeOnBackInvokedDispatcher mImeDispatcher;
+ private Boolean mBackCallbackRegistered = false;
+ private final OnBackInvokedCallback mCompatBackCallback = this::compatHandleBack;
/**
* Returns whether {@link InputMethodService} is responsible for rendering the back button and
@@ -797,7 +804,13 @@ public class InputMethodService extends AbstractInputMethodService {
@Override
public final void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
@NonNull EditorInfo editorInfo, boolean restarting,
- @NonNull IBinder startInputToken, @InputMethodNavButtonFlags int navButtonFlags) {
+ @NonNull IBinder startInputToken, @InputMethodNavButtonFlags int navButtonFlags,
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
+ mImeDispatcher = imeDispatcher;
+ if (mWindow != null) {
+ mWindow.getOnBackInvokedDispatcher().setImeOnBackInvokedDispatcher(
+ imeDispatcher);
+ }
mPrivOps.reportStartInputAsync(startInputToken);
mNavigationBarController.onNavButtonFlagsChanged(navButtonFlags);
if (restarting) {
@@ -1496,6 +1509,10 @@ public class InputMethodService extends AbstractInputMethodService {
Context.LAYOUT_INFLATER_SERVICE);
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initSoftInputWindow");
mWindow = new SoftInputWindow(this, mTheme, mDispatcherState);
+ if (mImeDispatcher != null) {
+ mWindow.getOnBackInvokedDispatcher()
+ .setImeOnBackInvokedDispatcher(mImeDispatcher);
+ }
mNavigationBarController.onSoftInputWindowCreated(mWindow);
{
final Window window = mWindow.getWindow();
@@ -1608,6 +1625,8 @@ public class InputMethodService extends AbstractInputMethodService {
// when IME developers are doing something unsupported.
InputMethodPrivilegedOperationsRegistry.remove(mToken);
}
+ unregisterCompatOnBackInvokedCallback();
+ mImeDispatcher = null;
}
/**
@@ -2568,8 +2587,46 @@ public class InputMethodService extends AbstractInputMethodService {
cancelImeSurfaceRemoval();
mInShowWindow = false;
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ registerCompatOnBackInvokedCallback();
+ }
+
+
+ /**
+ * Registers an {@link OnBackInvokedCallback} to handle back invocation when ahead-of-time
+ * back dispatching is enabled. We keep the {@link KeyEvent#KEYCODE_BACK} based legacy code
+ * around to handle back on older devices.
+ */
+ private void registerCompatOnBackInvokedCallback() {
+ if (mBackCallbackRegistered) {
+ return;
+ }
+ if (mWindow != null) {
+ mWindow.getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCompatBackCallback);
+ mBackCallbackRegistered = true;
+ }
+ }
+
+ private void unregisterCompatOnBackInvokedCallback() {
+ if (!mBackCallbackRegistered) {
+ return;
+ }
+ if (mWindow != null) {
+ mWindow.getOnBackInvokedDispatcher()
+ .unregisterOnBackInvokedCallback(mCompatBackCallback);
+ mBackCallbackRegistered = false;
+ }
}
+ private KeyEvent createBackKeyEvent(int action, boolean isTracking) {
+ final long when = SystemClock.uptimeMillis();
+ return new KeyEvent(when, when, action,
+ KeyEvent.KEYCODE_BACK, 0 /* repeat */, 0 /* metaState */,
+ KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
+ KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY
+ | (isTracking ? KeyEvent.FLAG_TRACKING : 0),
+ InputDevice.SOURCE_KEYBOARD);
+ }
private boolean prepareWindow(boolean showInput) {
boolean doShowInput = false;
@@ -2658,6 +2715,7 @@ public class InputMethodService extends AbstractInputMethodService {
}
mLastWasInFullscreenMode = mIsFullscreen;
updateFullscreenMode();
+ unregisterCompatOnBackInvokedCallback();
}
/**
@@ -3797,4 +3855,14 @@ public class InputMethodService extends AbstractInputMethodService {
proto.end(token);
}
};
+
+ private void compatHandleBack() {
+ final KeyEvent downEvent = createBackKeyEvent(
+ KeyEvent.ACTION_DOWN, false /* isTracking */);
+ onKeyDown(KeyEvent.KEYCODE_BACK, downEvent);
+ final boolean hasStartedTracking =
+ (downEvent.getFlags() & KeyEvent.FLAG_START_TRACKING) != 0;
+ final KeyEvent upEvent = createBackKeyEvent(KeyEvent.ACTION_UP, hasStartedTracking);
+ onKeyUp(KeyEvent.KEYCODE_BACK, upEvent);
+ }
}
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index ecdc803c0074..022d213e84da 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -1361,6 +1361,18 @@ public class Environment {
return false;
}
+ // Apps with PROPERTY_NO_APP_DATA_STORAGE should not be allowed in scoped storage
+ final String packageName = AppGlobals.getInitialPackage();
+ try {
+ final PackageManager.Property noAppStorageProp = packageManager.getProperty(
+ PackageManager.PROPERTY_NO_APP_DATA_STORAGE, packageName);
+ if (noAppStorageProp != null && noAppStorageProp.getBoolean()) {
+ return false;
+ }
+ } catch (PackageManager.NameNotFoundException ignore) {
+ // Property not defined for the package
+ }
+
boolean defaultScopedStorage = Compatibility.isChangeEnabled(DEFAULT_SCOPED_STORAGE);
boolean forceEnableScopedStorage = Compatibility.isChangeEnabled(
FORCE_ENABLE_SCOPED_STORAGE);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index a64e63eacd56..196f2f94120e 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2179,7 +2179,10 @@ public class UserManager {
}
} else {
UserInfo userInfo = getUserInfo(mUserId);
- return userInfo == null ? "" : userInfo.name;
+ if (userInfo != null && userInfo.name != null) {
+ return userInfo.name;
+ }
+ return "";
}
}
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 0e5a65ce71b7..77c00676878c 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -1292,7 +1292,8 @@ public class CallLog {
USER_MISSED_LOW_RING_VOLUME,
USER_MISSED_NO_VIBRATE,
USER_MISSED_CALL_SCREENING_SERVICE_SILENCED,
- USER_MISSED_CALL_FILTERS_TIMEOUT
+ USER_MISSED_CALL_FILTERS_TIMEOUT,
+ USER_MISSED_NEVER_RANG
})
@Retention(RetentionPolicy.SOURCE)
public @interface MissedReason {}
@@ -1383,6 +1384,13 @@ public class CallLog {
public static final long USER_MISSED_CALL_FILTERS_TIMEOUT = 1 << 22;
/**
+ * When {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE}, set this bit when
+ * the call ended before ringing.
+ * @hide
+ */
+ public static final long USER_MISSED_NEVER_RANG = 1 << 23;
+
+ /**
* Where the {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE},
* indicates factors which may have lead the user to miss the call.
* <P>Type: INTEGER</P>
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 37f44e98c165..9a2f7baa7265 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -738,6 +738,14 @@ public final class DeviceConfig {
*/
public static final String NAMESPACE_VENDOR_SYSTEM_NATIVE = "vendor_system_native";
+ /**
+ * Namespace for DevicePolicyManager related features.
+ *
+ * @hide
+ */
+ public static final String NAMESPACE_DEVICE_POLICY_MANAGER =
+ "device_policy_manager";
+
private static final Object sLock = new Object();
@GuardedBy("sLock")
private static ArrayMap<OnPropertiesChangedListener, Pair<String, Executor>> sListeners =
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index dac54cf6146e..f35a45891545 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9695,6 +9695,40 @@ public final class Settings {
"active_unlock_on_biometric_fail";
/**
+ * If active unlock triggers on biometric failures, include the following error codes
+ * as a biometric failure. See {@link android.hardware.biometrics.BiometricFaceConstants}.
+ * Error codes should be separated by a pipe. For example: "1|4|5". If active unlock
+ * should never trigger on any face errors, this should be set to an empty string.
+ * A null value will use the system default value (TIMEOUT).
+ * @hide
+ */
+ public static final String ACTIVE_UNLOCK_ON_FACE_ERRORS =
+ "active_unlock_on_face_errors";
+
+ /**
+ * If active unlock triggers on biometric failures, include the following acquired info
+ * as a "biometric failure". See {@link android.hardware.biometrics.BiometricFaceConstants}.
+ * Acquired codes should be separated by a pipe. For example: "1|4|5". If active unlock
+ * should never on trigger on any acquired info messages, this should be
+ * set to an empty string. A null value will use the system default value (none).
+ * @hide
+ */
+ public static final String ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO =
+ "active_unlock_on_face_acquire_info";
+
+ /**
+ * If active unlock triggers on biometric failures, then also request active unlock on
+ * unlock intent when each setting (BiometricType) is the only biometric type enrolled.
+ * Biometric types should be separated by a pipe. For example: "0|3" or "0". If this
+ * setting should be disabled, then this should be set to an empty string. A null value
+ * will use the system default value (0 / None).
+ * 0 = None, 1 = Any face, 2 = Any fingerprint, 3 = Under display fingerprint
+ * @hide
+ */
+ public static final String ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED =
+ "active_unlock_on_unlock_intent_when_biometric_enrolled";
+
+ /**
* Whether the assist gesture should be enabled.
*
* @hide
diff --git a/core/java/android/service/quickaccesswallet/TEST_MAPPING b/core/java/android/service/quickaccesswallet/TEST_MAPPING
index 4d97ab6e612d..5d2a3a806a7b 100644
--- a/core/java/android/service/quickaccesswallet/TEST_MAPPING
+++ b/core/java/android/service/quickaccesswallet/TEST_MAPPING
@@ -1,5 +1,5 @@
{
- "presubmit": [
+ "presubmit-large": [
{
"name": "CtsQuickAccessWalletTestCases"
}
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 3be4c3edc10e..24ded932b636 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -50,12 +50,21 @@ public class FeatureFlagUtils {
public static final String SETTINGS_ENABLE_SECURITY_HUB = "settings_enable_security_hub";
/** @hide */
public static final String SETTINGS_SUPPORT_LARGE_SCREEN = "settings_support_large_screen";
+
/**
* Support per app's language selection
* @hide
*/
public static final String SETTINGS_APP_LANGUAGE_SELECTION = "settings_app_language_selection";
+ /**
+ * Support locale opt-out and opt-in switch for per app's language.
+ * @hide
+ */
+ public static final String SETTINGS_APP_LOCALE_OPT_IN_ENABLED =
+ "settings_app_locale_opt_in_enabled";
+
+
/** @hide */
public static final String SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS =
"settings_enable_monitor_phantom_procs";
@@ -97,6 +106,7 @@ public class FeatureFlagUtils {
DEFAULT_FLAGS.put(SETTINGS_SUPPORT_LARGE_SCREEN, "true");
DEFAULT_FLAGS.put("settings_search_always_expand", "true");
DEFAULT_FLAGS.put(SETTINGS_APP_LANGUAGE_SELECTION, "true");
+ DEFAULT_FLAGS.put(SETTINGS_APP_LOCALE_OPT_IN_ENABLED, "true");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true");
DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true");
DEFAULT_FLAGS.put(SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE, "true");
@@ -106,6 +116,7 @@ public class FeatureFlagUtils {
static {
PERSISTENT_FLAGS = new HashSet<>();
PERSISTENT_FLAGS.add(SETTINGS_APP_LANGUAGE_SELECTION);
+ PERSISTENT_FLAGS.add(SETTINGS_APP_LOCALE_OPT_IN_ENABLED);
PERSISTENT_FLAGS.add(SETTINGS_SUPPORT_LARGE_SCREEN);
PERSISTENT_FLAGS.add(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS);
PERSISTENT_FLAGS.add(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME);
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index fd557e79c9c1..2e48c2b77233 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -719,9 +719,14 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
private void releaseSurfaces(boolean releaseSurfacePackage) {
mSurfaceAlpha = 1f;
-
- synchronized (mSurfaceControlLock) {
+
+ mSurfaceLock.lock();
+ try {
mSurface.destroy();
+ } finally {
+ mSurfaceLock.unlock();
+ }
+ synchronized (mSurfaceControlLock) {
if (mBlastBufferQueue != null) {
mBlastBufferQueue.destroy();
mBlastBufferQueue = null;
@@ -770,105 +775,99 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
Transaction surfaceUpdateTransaction) {
boolean realSizeChanged = false;
- mSurfaceLock.lock();
- try {
- mDrawingStopped = !mVisible;
+ mDrawingStopped = !mVisible;
- if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
- + "Cur surface: " + mSurface);
-
- // If we are creating the surface control or the parent surface has not
- // changed, then set relative z. Otherwise allow the parent
- // SurfaceChangedCallback to update the relative z. This is needed so that
- // we do not change the relative z before the server is ready to swap the
- // parent surface.
- if (creating) {
- updateRelativeZ(surfaceUpdateTransaction);
- if (mSurfacePackage != null) {
- reparentSurfacePackage(surfaceUpdateTransaction, mSurfacePackage);
- }
- }
- mParentSurfaceSequenceId = viewRoot.getSurfaceSequenceId();
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ + "Cur surface: " + mSurface);
- if (mViewVisibility) {
- surfaceUpdateTransaction.show(mSurfaceControl);
- } else {
- surfaceUpdateTransaction.hide(mSurfaceControl);
+ // If we are creating the surface control or the parent surface has not
+ // changed, then set relative z. Otherwise allow the parent
+ // SurfaceChangedCallback to update the relative z. This is needed so that
+ // we do not change the relative z before the server is ready to swap the
+ // parent surface.
+ if (creating) {
+ updateRelativeZ(surfaceUpdateTransaction);
+ if (mSurfacePackage != null) {
+ reparentSurfacePackage(surfaceUpdateTransaction, mSurfacePackage);
}
+ }
+ mParentSurfaceSequenceId = viewRoot.getSurfaceSequenceId();
+ if (mViewVisibility) {
+ surfaceUpdateTransaction.show(mSurfaceControl);
+ } else {
+ surfaceUpdateTransaction.hide(mSurfaceControl);
+ }
- updateBackgroundVisibility(surfaceUpdateTransaction);
- updateBackgroundColor(surfaceUpdateTransaction);
- if (mUseAlpha) {
- float alpha = getFixedAlpha();
- surfaceUpdateTransaction.setAlpha(mSurfaceControl, alpha);
- mSurfaceAlpha = alpha;
- }
- surfaceUpdateTransaction.setCornerRadius(mSurfaceControl, mCornerRadius);
- if ((sizeChanged || hintChanged) && !creating) {
- setBufferSize(surfaceUpdateTransaction);
- }
- if (sizeChanged || creating || !isHardwareAccelerated()) {
-
- // Set a window crop when creating the surface or changing its size to
- // crop the buffer to the surface size since the buffer producer may
- // use SCALING_MODE_SCALE and submit a larger size than the surface
- // size.
- if (mClipSurfaceToBounds && mClipBounds != null) {
- surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mClipBounds);
- } else {
- surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mSurfaceWidth,
- mSurfaceHeight);
- }
+ updateBackgroundVisibility(surfaceUpdateTransaction);
+ updateBackgroundColor(surfaceUpdateTransaction);
+ if (mUseAlpha) {
+ float alpha = getFixedAlpha();
+ surfaceUpdateTransaction.setAlpha(mSurfaceControl, alpha);
+ mSurfaceAlpha = alpha;
+ }
- surfaceUpdateTransaction.setDesintationFrame(mBlastSurfaceControl, mSurfaceWidth,
- mSurfaceHeight);
-
- if (isHardwareAccelerated()) {
- // This will consume the passed in transaction and the transaction will be
- // applied on a render worker thread.
- replacePositionUpdateListener(mSurfaceWidth, mSurfaceHeight);
- } else {
- onSetSurfacePositionAndScale(surfaceUpdateTransaction, mSurfaceControl,
- mScreenRect.left /*positionLeft*/,
- mScreenRect.top /*positionTop*/,
- mScreenRect.width() / (float) mSurfaceWidth /*postScaleX*/,
- mScreenRect.height() / (float) mSurfaceHeight /*postScaleY*/);
- }
- if (DEBUG_POSITION) {
- Log.d(TAG, String.format(
- "%d performSurfaceTransaction %s "
- + "position = [%d, %d, %d, %d] surfaceSize = %dx%d",
- System.identityHashCode(this),
- isHardwareAccelerated() ? "RenderWorker" : "UI Thread",
- mScreenRect.left, mScreenRect.top, mScreenRect.right,
- mScreenRect.bottom, mSurfaceWidth, mSurfaceHeight));
- }
+ surfaceUpdateTransaction.setCornerRadius(mSurfaceControl, mCornerRadius);
+ if ((sizeChanged || hintChanged) && !creating) {
+ setBufferSize(surfaceUpdateTransaction);
+ }
+ if (sizeChanged || creating || !isHardwareAccelerated()) {
+ // Set a window crop when creating the surface or changing its size to
+ // crop the buffer to the surface size since the buffer producer may
+ // use SCALING_MODE_SCALE and submit a larger size than the surface
+ // size.
+ if (mClipSurfaceToBounds && mClipBounds != null) {
+ surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mClipBounds);
+ } else {
+ surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mSurfaceWidth,
+ mSurfaceHeight);
}
- applyTransactionOnVriDraw(surfaceUpdateTransaction);
- updateEmbeddedAccessibilityMatrix(false);
-
- mSurfaceFrame.left = 0;
- mSurfaceFrame.top = 0;
- if (translator == null) {
- mSurfaceFrame.right = mSurfaceWidth;
- mSurfaceFrame.bottom = mSurfaceHeight;
+
+ surfaceUpdateTransaction.setDesintationFrame(mBlastSurfaceControl, mSurfaceWidth,
+ mSurfaceHeight);
+
+ if (isHardwareAccelerated()) {
+ // This will consume the passed in transaction and the transaction will be
+ // applied on a render worker thread.
+ replacePositionUpdateListener(mSurfaceWidth, mSurfaceHeight);
} else {
- float appInvertedScale = translator.applicationInvertedScale;
- mSurfaceFrame.right = (int) (mSurfaceWidth * appInvertedScale + 0.5f);
- mSurfaceFrame.bottom = (int) (mSurfaceHeight * appInvertedScale + 0.5f);
+ onSetSurfacePositionAndScale(surfaceUpdateTransaction, mSurfaceControl,
+ mScreenRect.left /*positionLeft*/,
+ mScreenRect.top /*positionTop*/,
+ mScreenRect.width() / (float) mSurfaceWidth /*postScaleX*/,
+ mScreenRect.height() / (float) mSurfaceHeight /*postScaleY*/);
}
- final int surfaceWidth = mSurfaceFrame.right;
- final int surfaceHeight = mSurfaceFrame.bottom;
- realSizeChanged = mLastSurfaceWidth != surfaceWidth
- || mLastSurfaceHeight != surfaceHeight;
- mLastSurfaceWidth = surfaceWidth;
- mLastSurfaceHeight = surfaceHeight;
- } finally {
- mSurfaceLock.unlock();
+ if (DEBUG_POSITION) {
+ Log.d(TAG, String.format(
+ "%d performSurfaceTransaction %s "
+ + "position = [%d, %d, %d, %d] surfaceSize = %dx%d",
+ System.identityHashCode(this),
+ isHardwareAccelerated() ? "RenderWorker" : "UI Thread",
+ mScreenRect.left, mScreenRect.top, mScreenRect.right,
+ mScreenRect.bottom, mSurfaceWidth, mSurfaceHeight));
+ }
+ }
+ applyTransactionOnVriDraw(surfaceUpdateTransaction);
+ updateEmbeddedAccessibilityMatrix(false);
+ mSurfaceFrame.left = 0;
+ mSurfaceFrame.top = 0;
+ if (translator == null) {
+ mSurfaceFrame.right = mSurfaceWidth;
+ mSurfaceFrame.bottom = mSurfaceHeight;
+ } else {
+ float appInvertedScale = translator.applicationInvertedScale;
+ mSurfaceFrame.right = (int) (mSurfaceWidth * appInvertedScale + 0.5f);
+ mSurfaceFrame.bottom = (int) (mSurfaceHeight * appInvertedScale + 0.5f);
}
+ final int surfaceWidth = mSurfaceFrame.right;
+ final int surfaceHeight = mSurfaceFrame.bottom;
+ realSizeChanged = mLastSurfaceWidth != surfaceWidth
+ || mLastSurfaceHeight != surfaceHeight;
+ mLastSurfaceWidth = surfaceWidth;
+ mLastSurfaceHeight = surfaceHeight;
+
return realSizeChanged;
}
@@ -1103,21 +1102,30 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
* Surface for compatibility reasons.
*/
private void copySurface(boolean surfaceControlCreated, boolean bufferSizeChanged) {
- if (surfaceControlCreated) {
- mSurface.copyFrom(mBlastBufferQueue);
- }
-
- if (bufferSizeChanged && getContext().getApplicationInfo().targetSdkVersion
- < Build.VERSION_CODES.O) {
- // Some legacy applications use the underlying native {@link Surface} object
- // as a key to whether anything has changed. In these cases, updates to the
- // existing {@link Surface} will be ignored when the size changes.
- // Therefore, we must explicitly recreate the {@link Surface} in these
- // cases.
- if (mBlastBufferQueue != null) {
- mSurface.transferFrom(mBlastBufferQueue.createSurfaceWithHandle());
- }
- }
+ // Some legacy applications use the underlying native {@link Surface} object
+ // as a key to whether anything has changed. In these cases, updates to the
+ // existing {@link Surface} will be ignored when the size changes.
+ // Therefore, we must explicitly recreate the {@link Surface} in these
+ // cases.
+ boolean needsWorkaround = bufferSizeChanged &&
+ getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.O;
+ if (!surfaceControlCreated && !needsWorkaround) {
+ return;
+ }
+ mSurfaceLock.lock();
+ try {
+ if (surfaceControlCreated) {
+ mSurface.copyFrom(mBlastBufferQueue);
+ }
+
+ if (needsWorkaround) {
+ if (mBlastBufferQueue != null) {
+ mSurface.transferFrom(mBlastBufferQueue.createSurfaceWithHandle());
+ }
+ }
+ } finally {
+ mSurfaceLock.unlock();
+ }
}
private void setBufferSize(Transaction transaction) {
@@ -1200,8 +1208,10 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
}
mTransformHint = viewRoot.getBufferTransformHint();
mBlastSurfaceControl.setTransformHint(mTransformHint);
+
mBlastBufferQueue = new BLASTBufferQueue(name, false /* updateDestinationFrame */);
mBlastBufferQueue.update(mBlastSurfaceControl, mSurfaceWidth, mSurfaceHeight, mFormat);
+ mBlastBufferQueue.setTransactionHangCallback(ViewRootImpl.sTransactionHangCallback);
}
private void onDrawFinished() {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 8901d86e1f2a..d04b07c13b41 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -8206,24 +8206,25 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (canNotifyAutofillEnterExitEvent()) {
AutofillManager afm = getAutofillManager();
if (afm != null) {
- if (enter && isFocused()) {
+ if (enter) {
// We have not been laid out yet, hence cannot evaluate
// whether this view is visible to the user, we will do
// the evaluation once layout is complete.
if (!isLaidOut()) {
mPrivateFlags3 |= PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
} else if (isVisibleToUser()) {
- // TODO This is a potential problem that View gets focus before it's visible
- // to User. Ideally View should handle the event when isVisibleToUser()
- // becomes true where it should issue notifyViewEntered().
- afm.notifyViewEntered(this);
- } else {
- afm.enableFillRequestActivityStarted(this);
+ if (isFocused()) {
+ // TODO This is a potential problem that View gets focus before it's
+ // visible to User. Ideally View should handle the event when
+ // isVisibleToUser() becomes true where it should issue
+ // notifyViewEntered().
+ afm.notifyViewEntered(this);
+ } else {
+ afm.notifyViewEnteredForFillDialog(this);
+ }
}
- } else if (!enter && !isFocused()) {
+ } else if (!isFocused()) {
afm.notifyViewExited(this);
- } else if (enter) {
- afm.enableFillRequestActivityStarted(this);
}
}
}
@@ -9921,7 +9922,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* <ol>
* <li>It should only be called when content capture is enabled for the view.
* <li>It must call viewAppeared() before viewDisappeared()
- * <li>viewAppearead() can only be called when the view is visible and laidout
+ * <li>viewAppeared() can only be called when the view is visible and laid out
* <li>It should not call the same event twice.
* </ol>
*/
@@ -9998,6 +9999,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
Log.v(CONTENT_CAPTURE_LOG_TAG, "no AttachInfo on disappeared for " + this);
}
}
+
+ // We reset any translation state as views may be re-used (e.g., as in ListView and
+ // RecyclerView). We only need to do this for views important for content capture since
+ // views unimportant for content capture won't be translated anyway.
+ clearTranslationState();
}
}
@@ -12718,6 +12724,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * @hide
+ */
+ public void clearTranslationState() {
+ if (mViewTranslationCallback != null) {
+ mViewTranslationCallback.onClearTranslation(this);
+ }
+ clearViewTranslationCallback();
+ clearViewTranslationResponse();
+ if (hasTranslationTransientState()) {
+ setHasTransientState(false);
+ setHasTranslationTransientState(false);
+ }
+ }
+
+ /**
* Returns true if this view is currently attached to a window.
*/
public boolean isAttachedToWindow() {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 42b5691b239e..48c102bd4883 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -590,7 +590,6 @@ public final class ViewRootImpl implements ViewParent,
@Nullable
int mContentCaptureEnabled = CONTENT_CAPTURE_ENABLED_NOT_CHECKED;
boolean mPerformContentCapture;
- boolean mPerformAutoFill;
boolean mReportNextDraw;
@@ -858,6 +857,28 @@ public final class ViewRootImpl implements ViewParent,
*/
private Bundle mRelayoutBundle = new Bundle();
+ private static volatile boolean sAnrReported = false;
+ static BLASTBufferQueue.TransactionHangCallback sTransactionHangCallback =
+ new BLASTBufferQueue.TransactionHangCallback() {
+ @Override
+ public void onTransactionHang(boolean isGPUHang) {
+ if (isGPUHang && !sAnrReported) {
+ sAnrReported = true;
+ try {
+ ActivityManager.getService().appNotResponding(
+ "Buffer processing hung up due to stuck fence. Indicates GPU hang");
+ } catch (RemoteException e) {
+ // We asked the system to crash us, but the system
+ // already crashed. Unfortunately things may be
+ // out of control.
+ }
+ } else {
+ // TODO: Do something with this later. For now we just ANR
+ // in dequeue buffer later like we always have.
+ }
+ }
+ };
+
private String mTag = TAG;
public ViewRootImpl(Context context, Display display) {
@@ -890,7 +911,6 @@ public final class ViewRootImpl implements ViewParent,
mPreviousTransparentRegion = new Region();
mFirst = true; // true for the first time the view is added
mPerformContentCapture = true; // also true for the first time the view is added
- mPerformAutoFill = true;
mAdded = false;
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
context);
@@ -2100,6 +2120,7 @@ public final class ViewRootImpl implements ViewParent,
}
mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl,
mSurfaceSize.x, mSurfaceSize.y, mWindowAttributes.format);
+ mBlastBufferQueue.setTransactionHangCallback(sTransactionHangCallback);
Surface blastSurface = mBlastBufferQueue.createSurface();
// Only call transferFrom if the surface has changed to prevent inc the generation ID and
// causing EGL resources to be recreated.
@@ -4308,18 +4329,6 @@ public final class ViewRootImpl implements ViewParent,
if (mPerformContentCapture) {
performContentCaptureInitialReport();
}
-
- if (mPerformAutoFill) {
- notifyEnterForAutoFillIfNeeded();
- }
- }
-
- private void notifyEnterForAutoFillIfNeeded() {
- mPerformAutoFill = false;
- final AutofillManager afm = getAutofillManager();
- if (afm != null) {
- afm.notifyViewEnteredForActivityStarted(mView);
- }
}
/**
@@ -6086,6 +6095,28 @@ public final class ViewRootImpl implements ViewParent,
@Override
protected int onProcess(QueuedInputEvent q) {
+ if (q.mEvent instanceof KeyEvent) {
+ final KeyEvent event = (KeyEvent) q.mEvent;
+
+ // If the new back dispatch is enabled, intercept KEYCODE_BACK before it reaches the
+ // view tree or IME, and invoke the appropriate {@link OnBackInvokedCallback}.
+ if (isBack(event)
+ && mContext != null
+ && WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) {
+ OnBackInvokedCallback topCallback =
+ getOnBackInvokedDispatcher().getTopCallback();
+ if (event.getAction() == KeyEvent.ACTION_UP) {
+ if (topCallback != null) {
+ topCallback.onBackInvoked();
+ return FINISH_HANDLED;
+ }
+ } else {
+ // Drop other actions such as {@link KeyEvent.ACTION_DOWN}.
+ return FINISH_NOT_HANDLED;
+ }
+ }
+ }
+
if (mInputQueue != null && q.mEvent instanceof KeyEvent) {
mInputQueue.sendInputEvent(q.mEvent, q, true, this);
return DEFER;
@@ -6437,24 +6468,6 @@ public final class ViewRootImpl implements ViewParent,
return FINISH_HANDLED;
}
- // If the new back dispatch is enabled, intercept KEYCODE_BACK before it reaches the
- // view tree and invoke the appropriate {@link OnBackInvokedCallback}.
- if (isBack(event)
- && mContext != null
- && WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) {
- OnBackInvokedCallback topCallback =
- getOnBackInvokedDispatcher().getTopCallback();
- if (event.getAction() == KeyEvent.ACTION_UP) {
- if (topCallback != null) {
- topCallback.onBackInvoked();
- return FINISH_HANDLED;
- }
- } else {
- // Drop other actions such as {@link KeyEvent.ACTION_DOWN}.
- return FINISH_NOT_HANDLED;
- }
- }
-
// Deliver the key to the view hierarchy.
if (mView.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 0a75992811f4..dcedb3083f76 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -102,6 +102,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
import sun.misc.Cleaner;
@@ -645,16 +646,6 @@ public final class AutofillManager {
private boolean mEnabledForAugmentedAutofillOnly;
/**
- * Indicates whether there are any fields that need to do a fill request
- * after the activity starts.
- *
- * Note: This field will be set to true multiple times if there are many
- * autofillable views. So needs to check mIsFillRequested at the same time to
- * avoid re-trigger autofill.
- */
- private boolean mRequireAutofill;
-
- /**
* Indicates whether there is already a field to do a fill request after
* the activity started.
*
@@ -663,7 +654,7 @@ public final class AutofillManager {
* triggered autofill, it is unnecessary to trigger again through
* AutofillManager#notifyViewEnteredForActivityStarted.
*/
- private boolean mIsFillRequested;
+ private AtomicBoolean mIsFillRequested;
@Nullable private List<AutofillId> mFillDialogTriggerIds;
@@ -811,8 +802,7 @@ public final class AutofillManager {
mContext = Objects.requireNonNull(context, "context cannot be null");
mService = service;
mOptions = context.getAutofillOptions();
- mIsFillRequested = false;
- mRequireAutofill = false;
+ mIsFillRequested = new AtomicBoolean(false);
mIsFillDialogEnabled = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_AUTOFILL,
@@ -1113,22 +1103,31 @@ public final class AutofillManager {
}
/**
- * The view have the allowed autofill hints, marked to perform a fill request after layout if
- * the field does not trigger a fill request.
+ * The {@link #DEVICE_CONFIG_AUTOFILL_DIALOG_ENABLED} is {@code true} or the view have
+ * the allowed autofill hints, performs a fill request to know there is any field supported
+ * fill dialog.
*
* @hide
*/
- public void enableFillRequestActivityStarted(View v) {
- if (mRequireAutofill) {
+ public void notifyViewEnteredForFillDialog(View v) {
+ // Skip if the fill request has been performed for a view.
+ if (mIsFillRequested.get()) {
return;
}
if (mIsFillDialogEnabled
|| ArrayUtils.containsAny(v.getAutofillHints(), mFillDialogEnabledHints)) {
if (sDebug) {
- Log.d(TAG, "Trigger fill request at starting");
+ Log.d(TAG, "Trigger fill request at view entered");
}
- mRequireAutofill = true;
+
+ // Note: No need for atomic getAndSet as this method is called on the UI thread.
+ mIsFillRequested.set(true);
+
+ int flags = FLAG_SUPPORTS_FILL_DIALOG;
+ flags |= FLAG_VIEW_NOT_FOCUSED;
+ // use root view, so autofill UI does not trigger immediately.
+ notifyViewEntered(v.getRootView(), flags);
}
}
@@ -1136,25 +1135,6 @@ public final class AutofillManager {
return mIsFillDialogEnabled || !ArrayUtils.isEmpty(mFillDialogEnabledHints);
}
- /**
- * Notify autofill to do a fill request while the activity started.
- *
- * @hide
- */
- public void notifyViewEnteredForActivityStarted(@NonNull View view) {
- if (!hasAutofillFeature() || !hasFillDialogUiFeature()) {
- return;
- }
-
- if (!mRequireAutofill || mIsFillRequested) {
- return;
- }
-
- int flags = FLAG_SUPPORTS_FILL_DIALOG;
- flags |= FLAG_VIEW_NOT_FOCUSED;
- notifyViewEntered(view, flags);
- }
-
private int getImeStateFlag(View v) {
final WindowInsets rootWindowInsets = v.getRootWindowInsets();
if (rootWindowInsets != null && rootWindowInsets.isVisible(WindowInsets.Type.ime())) {
@@ -1203,7 +1183,7 @@ public final class AutofillManager {
}
AutofillCallback callback;
synchronized (mLock) {
- mIsFillRequested = true;
+ mIsFillRequested.set(true);
callback = notifyViewEnteredLocked(view, flags);
}
@@ -2119,8 +2099,7 @@ public final class AutofillManager {
mFillableIds = null;
mSaveTriggerId = null;
mIdShownFillUi = null;
- mIsFillRequested = false;
- mRequireAutofill = false;
+ mIsFillRequested.set(false);
mShowAutofillDialogCalled = false;
mFillDialogTriggerIds = null;
if (resetEnteredIds) {
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index 6209b46997e8..dbdc0daff2c1 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -29,6 +29,7 @@ import android.util.Log;
import android.view.InputChannel;
import android.view.MotionEvent;
import android.view.View;
+import android.window.ImeOnBackInvokedDispatcher;
import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
import com.android.internal.inputmethod.InputMethodNavButtonFlags;
@@ -232,6 +233,10 @@ public interface InputMethod {
* long as your implementation of {@link InputMethod} relies on such
* IPCs
* @param navButtonFlags {@link InputMethodNavButtonFlags} in the initial state of this session.
+ * @param imeDispatcher The {@link ImeOnBackInvokedDispatcher }} to be set on the
+ * IME's {@link android.window.WindowOnBackInvokedDispatcher}, so that IME
+ * {@link android.window.OnBackInvokedCallback}s can be forwarded to
+ * the client requesting to start input.
* @see #startInput(InputConnection, EditorInfo)
* @see #restartInput(InputConnection, EditorInfo)
* @see EditorInfo
@@ -240,7 +245,8 @@ public interface InputMethod {
@MainThread
default void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
@NonNull EditorInfo editorInfo, boolean restarting,
- @NonNull IBinder startInputToken, @InputMethodNavButtonFlags int navButtonFlags) {
+ @NonNull IBinder startInputToken, @InputMethodNavButtonFlags int navButtonFlags,
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
if (restarting) {
restartInput(inputConnection, editorInfo);
} else {
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index d9bde5825fde..e2e9a8557793 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -91,6 +91,8 @@ import android.view.View;
import android.view.ViewRootImpl;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import android.view.autofill.AutofillManager;
+import android.window.ImeOnBackInvokedDispatcher;
+import android.window.WindowOnBackInvokedDispatcher;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.inputmethod.DirectBootAwareness;
@@ -105,6 +107,7 @@ import com.android.internal.inputmethod.StartInputFlags;
import com.android.internal.inputmethod.StartInputReason;
import com.android.internal.inputmethod.UnbindReason;
import com.android.internal.os.SomeArgs;
+import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethodClient;
import com.android.internal.view.IInputMethodManager;
import com.android.internal.view.IInputMethodSession;
@@ -279,6 +282,21 @@ public final class InputMethodManager {
private static final String SUBTYPE_MODE_VOICE = "voice";
/**
+ * Provide this to {@link IInputMethodManager#startInputOrWindowGainedFocus(
+ * int, IInputMethodClient, IBinder, int, int, int, EditorInfo, IInputContext, int)} to receive
+ * {@link android.window.OnBackInvokedCallback} registrations from IME.
+ */
+ private final ImeOnBackInvokedDispatcher mImeDispatcher =
+ new ImeOnBackInvokedDispatcher(Handler.getMain()) {
+ @Override
+ public WindowOnBackInvokedDispatcher getReceivingDispatcher() {
+ synchronized (mH) {
+ return mCurRootView != null ? mCurRootView.getOnBackInvokedDispatcher() : null;
+ }
+ }
+ };
+
+ /**
* Ensures that {@link #sInstance} becomes non-{@code null} for application that have directly
* or indirectly relied on {@link #sInstance} via reflection or something like that.
*
@@ -740,7 +758,8 @@ public final class InputMethodManager {
windowFlags,
null,
null, null,
- mCurRootView.mContext.getApplicationInfo().targetSdkVersion);
+ mCurRootView.mContext.getApplicationInfo().targetSdkVersion,
+ mImeDispatcher);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1687,6 +1706,8 @@ public final class InputMethodManager {
mServedConnecting = false;
clearConnectionLocked();
}
+ // Clear the back callbacks held by the ime dispatcher to avoid memory leaks.
+ mImeDispatcher.clear();
}
public void displayCompletions(View view, CompletionInfo[] completions) {
@@ -2359,7 +2380,8 @@ public final class InputMethodManager {
softInputMode, windowFlags, tba, servedInputConnection,
servedInputConnection == null ? null
: servedInputConnection.asIRemoteAccessibilityInputConnection(),
- view.getContext().getApplicationInfo().targetSdkVersion);
+ view.getContext().getApplicationInfo().targetSdkVersion,
+ mImeDispatcher);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java
index 8cf032bc03cb..6bf2474beb17 100644
--- a/core/java/android/view/translation/UiTranslationController.java
+++ b/core/java/android/view/translation/UiTranslationController.java
@@ -158,12 +158,7 @@ public class UiTranslationController implements Dumpable {
case STATE_UI_TRANSLATION_FINISHED:
destroyTranslators();
runForEachView((view, callback) -> {
- callback.onClearTranslation(view);
- view.clearViewTranslationResponse();
- if (view.hasTranslationTransientState()) {
- view.setHasTransientState(false);
- view.setHasTranslationTransientState(false);
- }
+ view.clearTranslationState();
});
notifyTranslationFinished(/* activityDestroyed= */ false);
synchronized (mLock) {
diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.aidl b/core/java/android/window/ImeOnBackInvokedDispatcher.aidl
new file mode 100644
index 000000000000..04e64203dd39
--- /dev/null
+++ b/core/java/android/window/ImeOnBackInvokedDispatcher.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/os/ParcelFileDescriptor.aidl
+**
+** 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.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.window;
+
+parcelable ImeOnBackInvokedDispatcher;
diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java
new file mode 100644
index 000000000000..d5763aa25884
--- /dev/null
+++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.util.Log;
+
+import java.util.HashMap;
+
+/**
+ * A {@link OnBackInvokedDispatcher} for IME that forwards {@link OnBackInvokedCallback}
+ * registrations from the IME process to the app process to be registered on the app window.
+ * <p>
+ * The app process creates and propagates an instance of {@link ImeOnBackInvokedDispatcher}
+ * to the IME to be set on the IME window's {@link WindowOnBackInvokedDispatcher}.
+ * <p>
+ * @see WindowOnBackInvokedDispatcher#setImeOnBackInvokedDispatcher
+ *
+ * @hide
+ */
+public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parcelable {
+
+ private static final String TAG = "ImeBackDispatcher";
+ static final String RESULT_KEY_ID = "id";
+ static final String RESULT_KEY_CALLBACK = "callback";
+ static final String RESULT_KEY_PRIORITY = "priority";
+ static final int RESULT_CODE_REGISTER = 0;
+ static final int RESULT_CODE_UNREGISTER = 1;
+ @NonNull
+ private final ResultReceiver mResultReceiver;
+
+ public ImeOnBackInvokedDispatcher(Handler handler) {
+ mResultReceiver = new ResultReceiver(handler) {
+ @Override
+ public void onReceiveResult(int resultCode, Bundle resultData) {
+ WindowOnBackInvokedDispatcher dispatcher = getReceivingDispatcher();
+ if (dispatcher != null) {
+ receive(resultCode, resultData, dispatcher);
+ }
+ }
+ };
+ }
+
+ /**
+ * Override this method to return the {@link WindowOnBackInvokedDispatcher} of the window
+ * that should receive the forwarded callback.
+ */
+ @Nullable
+ protected WindowOnBackInvokedDispatcher getReceivingDispatcher() {
+ return null;
+ }
+
+ ImeOnBackInvokedDispatcher(Parcel in) {
+ mResultReceiver = in.readTypedObject(ResultReceiver.CREATOR);
+ }
+
+ @Override
+ public void registerOnBackInvokedCallback(
+ @OnBackInvokedDispatcher.Priority int priority,
+ @NonNull OnBackInvokedCallback callback) {
+ final Bundle bundle = new Bundle();
+ final IOnBackInvokedCallback iCallback =
+ new WindowOnBackInvokedDispatcher.OnBackInvokedCallbackWrapper(callback);
+ bundle.putBinder(RESULT_KEY_CALLBACK, iCallback.asBinder());
+ bundle.putInt(RESULT_KEY_PRIORITY, priority);
+ bundle.putInt(RESULT_KEY_ID, callback.hashCode());
+ mResultReceiver.send(RESULT_CODE_REGISTER, bundle);
+ }
+
+ @Override
+ public void unregisterOnBackInvokedCallback(
+ @NonNull OnBackInvokedCallback callback) {
+ Bundle bundle = new Bundle();
+ bundle.putInt(RESULT_KEY_ID, callback.hashCode());
+ mResultReceiver.send(RESULT_CODE_UNREGISTER, bundle);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeTypedObject(mResultReceiver, flags);
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<ImeOnBackInvokedDispatcher> CREATOR =
+ new Parcelable.Creator<ImeOnBackInvokedDispatcher>() {
+ public ImeOnBackInvokedDispatcher createFromParcel(Parcel in) {
+ return new ImeOnBackInvokedDispatcher(in);
+ }
+ public ImeOnBackInvokedDispatcher[] newArray(int size) {
+ return new ImeOnBackInvokedDispatcher[size];
+ }
+ };
+
+ private final HashMap<Integer, OnBackInvokedCallback> mImeCallbackMap = new HashMap<>();
+
+ private void receive(
+ int resultCode, Bundle resultData,
+ @NonNull OnBackInvokedDispatcher receivingDispatcher) {
+ final int callbackId = resultData.getInt(RESULT_KEY_ID);
+ if (resultCode == RESULT_CODE_REGISTER) {
+ int priority = resultData.getInt(RESULT_KEY_PRIORITY);
+ final IOnBackInvokedCallback callback = IOnBackInvokedCallback.Stub.asInterface(
+ resultData.getBinder(RESULT_KEY_CALLBACK));
+ registerReceivedCallback(
+ callback, priority, callbackId, receivingDispatcher);
+ } else if (resultCode == RESULT_CODE_UNREGISTER) {
+ unregisterReceivedCallback(callbackId, receivingDispatcher);
+ }
+ }
+
+ private void registerReceivedCallback(
+ @NonNull IOnBackInvokedCallback iCallback,
+ @OnBackInvokedDispatcher.Priority int priority,
+ int callbackId,
+ @NonNull OnBackInvokedDispatcher receivingDispatcher) {
+ final ImeOnBackInvokedCallback imeCallback =
+ new ImeOnBackInvokedCallback(iCallback);
+ mImeCallbackMap.put(callbackId, imeCallback);
+ receivingDispatcher.registerOnBackInvokedCallback(priority, imeCallback);
+ }
+
+ private void unregisterReceivedCallback(
+ int callbackId, OnBackInvokedDispatcher receivingDispatcher) {
+ final OnBackInvokedCallback callback = mImeCallbackMap.get(callbackId);
+ if (callback == null) {
+ Log.e(TAG, "Ime callback not found. Ignoring unregisterReceivedCallback. "
+ + "callbackId: " + callbackId);
+ return;
+ }
+ receivingDispatcher.unregisterOnBackInvokedCallback(callback);
+ }
+
+ /** Clears all registered callbacks on the instance. */
+ public void clear() {
+ mImeCallbackMap.clear();
+ }
+
+ static class ImeOnBackInvokedCallback implements OnBackInvokedCallback {
+ @NonNull
+ private final IOnBackInvokedCallback mIOnBackInvokedCallback;
+
+ ImeOnBackInvokedCallback(@NonNull IOnBackInvokedCallback iCallback) {
+ mIOnBackInvokedCallback = iCallback;
+ }
+
+ @Override
+ public void onBackInvoked() {
+ try {
+ if (mIOnBackInvokedCallback != null) {
+ mIOnBackInvokedCallback.onBackInvoked();
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception when invoking forwarded callback. e: ", e);
+ }
+ }
+
+ IOnBackInvokedCallback getIOnBackInvokedCallback() {
+ return mIOnBackInvokedCallback;
+ }
+ }
+}
diff --git a/core/java/android/window/OnBackInvokedDispatcher.java b/core/java/android/window/OnBackInvokedDispatcher.java
index 6bc2b5043e79..3539049af219 100644
--- a/core/java/android/window/OnBackInvokedDispatcher.java
+++ b/core/java/android/window/OnBackInvokedDispatcher.java
@@ -96,4 +96,19 @@ public interface OnBackInvokedDispatcher {
* @hide
*/
default void registerSystemOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback) { }
+
+
+ /**
+ * Sets an {@link ImeOnBackInvokedDispatcher} to forward {@link OnBackInvokedCallback}s
+ * from IME to the app process to be registered on the app window.
+ *
+ * Only call this on the IME window. Create the {@link ImeOnBackInvokedDispatcher} from
+ * the application process and override
+ * {@link ImeOnBackInvokedDispatcher#getReceivingDispatcher()} to point to the app
+ * window's {@link WindowOnBackInvokedDispatcher}.
+ *
+ * @hide
+ */
+ default void setImeOnBackInvokedDispatcher(
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher) { }
}
diff --git a/core/java/android/window/ProxyOnBackInvokedDispatcher.java b/core/java/android/window/ProxyOnBackInvokedDispatcher.java
index 44093971a23e..bedf5038f669 100644
--- a/core/java/android/window/ProxyOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ProxyOnBackInvokedDispatcher.java
@@ -49,6 +49,7 @@ public class ProxyOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
private final List<Pair<OnBackInvokedCallback, Integer>> mCallbacks = new ArrayList<>();
private final Object mLock = new Object();
private OnBackInvokedDispatcher mActualDispatcher = null;
+ private ImeOnBackInvokedDispatcher mImeDispatcher;
@Override
public void registerOnBackInvokedCallback(
@@ -108,6 +109,9 @@ public class ProxyOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
Log.v(TAG, String.format("Proxy transferring %d callbacks to %s", mCallbacks.size(),
mActualDispatcher));
}
+ if (mImeDispatcher != null) {
+ mActualDispatcher.setImeOnBackInvokedDispatcher(mImeDispatcher);
+ }
for (Pair<OnBackInvokedCallback, Integer> callbackPair : mCallbacks) {
int priority = callbackPair.second;
if (priority >= 0) {
@@ -117,6 +121,7 @@ public class ProxyOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
}
}
mCallbacks.clear();
+ mImeDispatcher = null;
}
private void clearCallbacksOnDispatcher() {
@@ -142,6 +147,7 @@ public class ProxyOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
}
synchronized (mLock) {
mCallbacks.clear();
+ mImeDispatcher = null;
}
}
@@ -169,4 +175,14 @@ public class ProxyOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
transferCallbacksToDispatcher();
}
}
+
+ @Override
+ public void setImeOnBackInvokedDispatcher(
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
+ if (mActualDispatcher != null) {
+ mActualDispatcher.setImeOnBackInvokedDispatcher(imeDispatcher);
+ } else {
+ mImeDispatcher = imeDispatcher;
+ }
+ }
}
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 781859cecb2c..edfdbcc1f4f8 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -55,6 +55,8 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
.getInt("persist.wm.debug.predictive_back", 1) != 0;
private static final boolean ALWAYS_ENFORCE_PREDICTIVE_BACK = SystemProperties
.getInt("persist.wm.debug.predictive_back_always_enforce", 0) != 0;
+ @Nullable
+ private ImeOnBackInvokedDispatcher mImeDispatcher;
/** Convenience hashmap to quickly decide if a callback has been added. */
private final HashMap<OnBackInvokedCallback, Integer> mAllCallbacks = new HashMap<>();
@@ -94,6 +96,10 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
private void registerOnBackInvokedCallbackUnchecked(
@NonNull OnBackInvokedCallback callback, @Priority int priority) {
+ if (mImeDispatcher != null) {
+ mImeDispatcher.registerOnBackInvokedCallback(priority, callback);
+ return;
+ }
if (!mOnBackInvokedCallbacks.containsKey(priority)) {
mOnBackInvokedCallbacks.put(priority, new ArrayList<>());
}
@@ -120,6 +126,10 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
@Override
public void unregisterOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback) {
+ if (mImeDispatcher != null) {
+ mImeDispatcher.unregisterOnBackInvokedCallback(callback);
+ return;
+ }
if (!mAllCallbacks.containsKey(callback)) {
if (DEBUG) {
Log.i(TAG, "Callback not found. returning...");
@@ -153,6 +163,9 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
}
mAllCallbacks.clear();
mOnBackInvokedCallbacks.clear();
+ if (mImeDispatcher != null) {
+ mImeDispatcher = null;
+ }
}
private void setTopOnBackInvokedCallback(@Nullable OnBackInvokedCallback callback) {
@@ -160,14 +173,18 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
return;
}
try {
- if (callback == null) {
- mWindowSession.setOnBackInvokedCallbackInfo(mWindow, null);
- } else {
+ OnBackInvokedCallbackInfo callbackInfo = null;
+ if (callback != null) {
int priority = mAllCallbacks.get(callback);
- mWindowSession.setOnBackInvokedCallbackInfo(
- mWindow, new OnBackInvokedCallbackInfo(
- new OnBackInvokedCallbackWrapper(callback), priority));
+ final IOnBackInvokedCallback iCallback =
+ callback instanceof ImeOnBackInvokedDispatcher
+ .ImeOnBackInvokedCallback
+ ? ((ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback)
+ callback).getIOnBackInvokedCallback()
+ : new OnBackInvokedCallbackWrapper(callback);
+ callbackInfo = new OnBackInvokedCallbackInfo(iCallback, priority);
}
+ mWindowSession.setOnBackInvokedCallbackInfo(mWindow, callbackInfo);
if (DEBUG && callback == null) {
Log.d(TAG, TextUtils.formatSimple("setTopOnBackInvokedCallback(null) Callers:%s",
Debug.getCallers(5, " ")));
@@ -190,7 +207,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
return null;
}
- private static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub {
+ static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub {
private final WeakReference<OnBackInvokedCallback> mCallback;
OnBackInvokedCallbackWrapper(@NonNull OnBackInvokedCallback callback) {
@@ -270,4 +287,10 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
return featureFlagEnabled && (appRequestsPredictiveBack || ALWAYS_ENFORCE_PREDICTIVE_BACK);
}
+
+ @Override
+ public void setImeOnBackInvokedDispatcher(
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
+ mImeDispatcher = imeDispatcher;
+ }
}
diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java
index 7db4243d3a83..0976f45c02b0 100644
--- a/core/java/android/window/WindowTokenClient.java
+++ b/core/java/android/window/WindowTokenClient.java
@@ -21,8 +21,10 @@ import static android.window.ConfigurationHelper.shouldUpdateResources;
import android.annotation.AnyThread;
import android.annotation.BinderThread;
+import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityThread;
import android.app.IWindowToken;
import android.app.ResourcesManager;
import android.content.Context;
@@ -33,7 +35,6 @@ import android.os.Bundle;
import android.os.Debug;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
import android.view.IWindowManager;
@@ -42,6 +43,7 @@ import android.view.WindowManagerGlobal;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.function.pooled.PooledLambda;
import java.lang.ref.WeakReference;
@@ -76,7 +78,7 @@ public class WindowTokenClient extends IWindowToken.Stub {
private boolean mAttachToWindowContainer;
- private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private final Handler mHandler = ActivityThread.currentActivityThread().getHandler();
/**
* Attaches {@code context} to this {@link WindowTokenClient}. Each {@link WindowTokenClient}
@@ -188,8 +190,8 @@ public class WindowTokenClient extends IWindowToken.Stub {
@BinderThread
@Override
public void onConfigurationChanged(Configuration newConfig, int newDisplayId) {
- mHandler.post(() -> onConfigurationChanged(newConfig, newDisplayId,
- true /* shouldReportConfigChange */));
+ mHandler.post(PooledLambda.obtainRunnable(this::onConfigurationChanged, newConfig,
+ newDisplayId, true /* shouldReportConfigChange */).recycleOnUse());
}
// TODO(b/192048581): rewrite this method based on WindowContext and WindowProviderService
@@ -279,12 +281,16 @@ public class WindowTokenClient extends IWindowToken.Stub {
@BinderThread
@Override
public void onWindowTokenRemoved() {
- mHandler.post(() -> {
- final Context context = mContextRef.get();
- if (context != null) {
- context.destroy();
- mContextRef.clear();
- }
- });
+ mHandler.post(PooledLambda.obtainRunnable(
+ WindowTokenClient::onWindowTokenRemovedInner, this).recycleOnUse());
+ }
+
+ @MainThread
+ private void onWindowTokenRemovedInner() {
+ final Context context = mContextRef.get();
+ if (context != null) {
+ context.destroy();
+ mContextRef.clear();
+ }
}
}
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index f0a685ec4d2e..781b6d5459ca 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -245,7 +245,7 @@ public class ChooserActivity extends ResolverActivity implements
SystemUiDeviceConfigFlags.IS_NEARBY_SHARE_FIRST_TARGET_IN_RANKED_APP,
DEFAULT_IS_NEARBY_SHARE_FIRST_TARGET_IN_RANKED_APP);
- private static final int DEFAULT_LIST_VIEW_UPDATE_DELAY_MS = 250;
+ private static final int DEFAULT_LIST_VIEW_UPDATE_DELAY_MS = 125;
@VisibleForTesting
int mListViewUpdateDelayMs = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
@@ -971,7 +971,8 @@ public class ChooserActivity extends ResolverActivity implements
getChooserActivityLogger().logShareTargetSelected(
SELECTION_TYPE_COPY,
"",
- -1);
+ -1,
+ false);
setResult(RESULT_OK);
finish();
@@ -1155,7 +1156,8 @@ public class ChooserActivity extends ResolverActivity implements
getChooserActivityLogger().logShareTargetSelected(
SELECTION_TYPE_NEARBY,
"",
- -1);
+ -1,
+ false);
// Action bar is user-independent, always start as primary
safelyStartActivityAsUser(ti, getPersonalProfileUserHandle());
finish();
@@ -1177,7 +1179,8 @@ public class ChooserActivity extends ResolverActivity implements
getChooserActivityLogger().logShareTargetSelected(
SELECTION_TYPE_EDIT,
"",
- -1);
+ -1,
+ false);
// Action bar is user-independent, always start as primary
safelyStartActivityAsUser(ti, getPersonalProfileUserHandle());
finish();
@@ -1754,7 +1757,8 @@ public class ChooserActivity extends ResolverActivity implements
target.getComponentName().getPackageName()
+ target.getTitle().toString(),
mMaxHashSaltDays);
- directTargetAlsoRanked = getRankedPosition((SelectableTargetInfo) targetInfo);
+ SelectableTargetInfo selectableTargetInfo = (SelectableTargetInfo) targetInfo;
+ directTargetAlsoRanked = getRankedPosition(selectableTargetInfo);
if (mCallerChooserTargets != null) {
numCallerProvided = mCallerChooserTargets.length;
@@ -1762,7 +1766,8 @@ public class ChooserActivity extends ResolverActivity implements
getChooserActivityLogger().logShareTargetSelected(
SELECTION_TYPE_SERVICE,
targetInfo.getResolveInfo().activityInfo.processName,
- value
+ value,
+ selectableTargetInfo.isPinned()
);
break;
case ChooserListAdapter.TARGET_CALLER:
@@ -1773,7 +1778,8 @@ public class ChooserActivity extends ResolverActivity implements
getChooserActivityLogger().logShareTargetSelected(
SELECTION_TYPE_APP,
targetInfo.getResolveInfo().activityInfo.processName,
- value
+ value,
+ targetInfo.isPinned()
);
break;
case ChooserListAdapter.TARGET_STANDARD_AZ:
@@ -1784,7 +1790,8 @@ public class ChooserActivity extends ResolverActivity implements
getChooserActivityLogger().logShareTargetSelected(
SELECTION_TYPE_STANDARD,
targetInfo.getResolveInfo().activityInfo.processName,
- value
+ value,
+ false
);
break;
}
diff --git a/core/java/com/android/internal/app/ChooserActivityLogger.java b/core/java/com/android/internal/app/ChooserActivityLogger.java
index 321730786471..bb7d50af4200 100644
--- a/core/java/com/android/internal/app/ChooserActivityLogger.java
+++ b/core/java/com/android/internal/app/ChooserActivityLogger.java
@@ -34,7 +34,8 @@ public interface ChooserActivityLogger {
int appProvidedApp, boolean isWorkprofile, int previewType, String intent);
/** Logs a UiEventReported event for the system sharesheet when the user selects a target. */
- void logShareTargetSelected(int targetType, String packageName, int positionPicked);
+ void logShareTargetSelected(int targetType, String packageName, int positionPicked,
+ boolean isPinned);
/** Logs a UiEventReported event for the system sharesheet being triggered by the user. */
default void logSharesheetTriggered() {
diff --git a/core/java/com/android/internal/app/ChooserActivityLoggerImpl.java b/core/java/com/android/internal/app/ChooserActivityLoggerImpl.java
index 48bdba3f5dae..e3cc4f12fcc6 100644
--- a/core/java/com/android/internal/app/ChooserActivityLoggerImpl.java
+++ b/core/java/com/android/internal/app/ChooserActivityLoggerImpl.java
@@ -51,12 +51,14 @@ public class ChooserActivityLoggerImpl implements ChooserActivityLogger {
}
@Override
- public void logShareTargetSelected(int targetType, String packageName, int positionPicked) {
+ public void logShareTargetSelected(int targetType, String packageName, int positionPicked,
+ boolean isPinned) {
FrameworkStatsLog.write(FrameworkStatsLog.RANKING_SELECTED,
/* event_id = 1 */ SharesheetTargetSelectedEvent.fromTargetType(targetType).getId(),
/* package_name = 2 */ packageName,
/* instance_id = 3 */ getInstanceId().getId(),
- /* position_picked = 4 */ positionPicked);
+ /* position_picked = 4 */ positionPicked,
+ /* is_pinned = 5 */ isPinned);
}
@Override
diff --git a/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java b/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java
index c5b21ac4da90..e7f80a7f6071 100644
--- a/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java
+++ b/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java
@@ -589,7 +589,7 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator
MetricsLogger metricsLogger = new MetricsLogger();
LogMaker log = new LogMaker(MetricsEvent.ACTION_TARGET_SELECTED);
log.setComponentName(mRankerServiceName);
- log.addTaggedData(MetricsEvent.FIELD_IS_CATEGORY_USED, mAnnotationsUsed);
+ log.addTaggedData(MetricsEvent.FIELD_IS_CATEGORY_USED, mAnnotationsUsed ? 1 : 0);
log.addTaggedData(MetricsEvent.FIELD_RANKED_POSITION, selectedPos);
metricsLogger.write(log);
}
diff --git a/core/java/com/android/internal/app/UnlaunchableAppActivity.java b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
index 957a6365739d..e56d92b48528 100644
--- a/core/java/com/android/internal/app/UnlaunchableAppActivity.java
+++ b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
@@ -91,7 +91,12 @@ public class UnlaunchableAppActivity extends Activity
} else {
builder.setPositiveButton(R.string.ok, null);
}
- builder.show();
+ final AlertDialog dialog = builder.create();
+ dialog.create();
+ // Prevents screen overlay attack.
+ getWindow().setHideOverlayWindows(true);
+ dialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
+ dialog.show();
}
private String getDialogTitle() {
diff --git a/core/java/com/android/internal/logging/UiEventLoggerImpl.java b/core/java/com/android/internal/logging/UiEventLoggerImpl.java
index 983e0fe6144e..ffbf646a345c 100644
--- a/core/java/com/android/internal/logging/UiEventLoggerImpl.java
+++ b/core/java/com/android/internal/logging/UiEventLoggerImpl.java
@@ -69,7 +69,8 @@ public class UiEventLoggerImpl implements UiEventLogger {
/* event_id = 1 */ eventID,
/* package_name = 2 */ packageName,
/* instance_id = 3 */ 0,
- /* position_picked = 4 */ position);
+ /* position_picked = 4 */ position,
+ /* is_pinned = 5 */ false);
}
}
@@ -82,7 +83,8 @@ public class UiEventLoggerImpl implements UiEventLogger {
/* event_id = 1 */ eventID,
/* package_name = 2 */ packageName,
/* instance_id = 3 */ instance.getId(),
- /* position_picked = 4 */ position);
+ /* position_picked = 4 */ position,
+ /* is_pinned = 5 */ false);
} else if ((eventID > 0)) {
logWithPosition(event, uid, packageName, position);
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 46b463074383..ef8f2db5ff57 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -88,7 +88,7 @@ interface IStatusBarService
in int notificationLocation, boolean modifiedBeforeSending);
void onNotificationSettingsViewed(String key);
void onNotificationBubbleChanged(String key, boolean isBubble, int flags);
- void onBubbleNotificationSuppressionChanged(String key, boolean isNotifSuppressed, boolean isBubbleSuppressed);
+ void onBubbleMetadataFlagChanged(String key, int flags);
void hideCurrentInputMethodForBubbles();
void grantInlineReplyUriPermission(String key, in Uri uri, in UserHandle user, String packageName);
oneway void clearInlineReplyUriPermissions(String key);
diff --git a/core/java/com/android/internal/util/ImageUtils.java b/core/java/com/android/internal/util/ImageUtils.java
index 397b2c052514..62dea9dc82e0 100644
--- a/core/java/com/android/internal/util/ImageUtils.java
+++ b/core/java/com/android/internal/util/ImageUtils.java
@@ -137,6 +137,18 @@ public class ImageUtils {
*/
public static Bitmap buildScaledBitmap(Drawable drawable, int maxWidth,
int maxHeight) {
+ return buildScaledBitmap(drawable, maxWidth, maxHeight, false);
+ }
+
+ /**
+ * Convert a drawable to a bitmap, scaled to fit within maxWidth and maxHeight.
+ *
+ * @param allowUpscaling if true, the drawable will not only be scaled down, but also scaled up
+ * to fit within the maximum size given. This is useful for converting
+ * vectorized icons which usually have a very small intrinsic size.
+ */
+ public static Bitmap buildScaledBitmap(Drawable drawable, int maxWidth,
+ int maxHeight, boolean allowUpscaling) {
if (drawable == null) {
return null;
}
@@ -155,7 +167,9 @@ public class ImageUtils {
// a large notification icon if necessary
float ratio = Math.min((float) maxWidth / (float) originalWidth,
(float) maxHeight / (float) originalHeight);
- ratio = Math.min(1.0f, ratio);
+ if (!allowUpscaling) {
+ ratio = Math.min(1.0f, ratio);
+ }
int scaledWidth = (int) (ratio * originalWidth);
int scaledHeight = (int) (ratio * originalHeight);
Bitmap result = Bitmap.createBitmap(scaledWidth, scaledHeight, Config.ARGB_8888);
diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl
index 40d89db6165c..4e2526a281b3 100644
--- a/core/java/com/android/internal/view/IInputMethod.aidl
+++ b/core/java/com/android/internal/view/IInputMethod.aidl
@@ -23,6 +23,7 @@ import android.view.MotionEvent;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputMethodSubtype;
+import android.window.ImeOnBackInvokedDispatcher;
import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
import com.android.internal.view.IInlineSuggestionsRequestCallback;
import com.android.internal.view.IInputContext;
@@ -47,7 +48,8 @@ oneway interface IInputMethod {
void unbindInput();
void startInput(in IBinder startInputToken, in IInputContext inputContext,
- in EditorInfo attribute, boolean restarting, int navigationBarFlags);
+ in EditorInfo attribute, boolean restarting, int navigationBarFlags,
+ in ImeOnBackInvokedDispatcher imeDispatcher);
void onNavButtonFlagsChanged(int navButtonFlags);
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index d7bb2cb10b8c..315776045afe 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -20,6 +20,7 @@ import android.os.ResultReceiver;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
import android.view.inputmethod.EditorInfo;
+import android.window.ImeOnBackInvokedDispatcher;
import com.android.internal.inputmethod.InputBindResult;
import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
@@ -57,7 +58,7 @@ interface IInputMethodManager {
/* @android.view.WindowManager.LayoutParams.SoftInputModeFlags */ int softInputMode,
int windowFlags, in EditorInfo attribute, in IInputContext inputContext,
in IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
- int unverifiedTargetSdkVersion);
+ int unverifiedTargetSdkVersion, in ImeOnBackInvokedDispatcher imeDispatcher);
void showInputMethodPickerFromClient(in IInputMethodClient client,
int auxiliarySubtypeMode);
diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp
index c0f7b41d116d..4af28ea24361 100644
--- a/core/jni/android_graphics_BLASTBufferQueue.cpp
+++ b/core/jni/android_graphics_BLASTBufferQueue.cpp
@@ -47,6 +47,43 @@ static JNIEnv* getenv(JavaVM* vm) {
return env;
}
+ struct {
+ jmethodID onTransactionHang;
+} gTransactionHangCallback;
+
+class TransactionHangCallbackWrapper : public LightRefBase<TransactionHangCallbackWrapper> {
+public:
+ explicit TransactionHangCallbackWrapper(JNIEnv* env, jobject jobject) {
+ env->GetJavaVM(&mVm);
+ mTransactionHangObject = env->NewGlobalRef(jobject);
+ LOG_ALWAYS_FATAL_IF(!mTransactionHangObject, "Failed to make global ref");
+ }
+
+ ~TransactionHangCallbackWrapper() {
+ if (mTransactionHangObject) {
+ getenv()->DeleteGlobalRef(mTransactionHangObject);
+ mTransactionHangObject = nullptr;
+ }
+ }
+
+ void onTransactionHang(bool isGpuHang) {
+ if (mTransactionHangObject) {
+ getenv()->CallVoidMethod(mTransactionHangObject,
+ gTransactionHangCallback.onTransactionHang, isGpuHang);
+ }
+ }
+
+private:
+ JavaVM* mVm;
+ jobject mTransactionHangObject;
+
+ JNIEnv* getenv() {
+ JNIEnv* env;
+ mVm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
+ return env;
+ }
+};
+
static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring jName,
jboolean updateDestinationFrame) {
ScopedUtfChars name(env, jName);
@@ -141,6 +178,20 @@ static bool nativeIsSameSurfaceControl(JNIEnv* env, jclass clazz, jlong ptr, jlo
sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr);
return queue->isSameSurfaceControl(reinterpret_cast<SurfaceControl*>(surfaceControl));
}
+
+static void nativeSetTransactionHangCallback(JNIEnv* env, jclass clazz, jlong ptr,
+ jobject transactionHangCallback) {
+ sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr);
+ if (transactionHangCallback == nullptr) {
+ queue->setTransactionHangCallback(nullptr);
+ } else {
+ sp<TransactionHangCallbackWrapper> wrapper =
+ new TransactionHangCallbackWrapper{env, transactionHangCallback};
+ queue->setTransactionHangCallback([wrapper](bool isGpuHang) {
+ wrapper->onTransactionHang(isGpuHang);
+ });
+ }
+}
static jobject nativeGatherPendingTransactions(JNIEnv* env, jclass clazz, jlong ptr,
jlong frameNum) {
@@ -163,7 +214,10 @@ static const JNINativeMethod gMethods[] = {
{"nativeGetLastAcquiredFrameNum", "(J)J", (void*)nativeGetLastAcquiredFrameNum},
{"nativeApplyPendingTransactions", "(JJ)V", (void*)nativeApplyPendingTransactions},
{"nativeIsSameSurfaceControl", "(JJ)Z", (void*)nativeIsSameSurfaceControl},
- {"nativeGatherPendingTransactions", "(JJ)Landroid/view/SurfaceControl$Transaction;", (void*)nativeGatherPendingTransactions}
+ {"nativeGatherPendingTransactions", "(JJ)Landroid/view/SurfaceControl$Transaction;", (void*)nativeGatherPendingTransactions},
+ {"nativeSetTransactionHangCallback",
+ "(JLandroid/graphics/BLASTBufferQueue$TransactionHangCallback;)V",
+ (void*)nativeSetTransactionHangCallback},
// clang-format on
};
@@ -180,6 +234,11 @@ int register_android_graphics_BLASTBufferQueue(JNIEnv* env) {
jclass consumer = FindClassOrDie(env, "java/util/function/Consumer");
gTransactionConsumer.accept =
GetMethodIDOrDie(env, consumer, "accept", "(Ljava/lang/Object;)V");
+ jclass transactionHangClass =
+ FindClassOrDie(env, "android/graphics/BLASTBufferQueue$TransactionHangCallback");
+ gTransactionHangCallback.onTransactionHang =
+ GetMethodIDOrDie(env, transactionHangClass, "onTransactionHang", "(Z)V");
+
return 0;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d32acaf1e620..0f328b034f38 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7056,6 +7056,10 @@
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
+ <service android:name="com.android.server.notification.ReviewNotificationPermissionsJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE">
+ </service>
+
<service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader"
android:exported="false">
<intent-filter>
diff --git a/core/res/OWNERS b/core/res/OWNERS
index 95d2712a2b41..c54638a368a2 100644
--- a/core/res/OWNERS
+++ b/core/res/OWNERS
@@ -45,3 +45,4 @@ per-file res/xml/power_profile_test.xml = file:/BATTERY_STATS_OWNERS
# Telephony
per-file res/values/config_telephony.xml = file:/platform/frameworks/opt/telephony:/OWNERS
+per-file res/xml/sms_short_codes.xml = file:/platform/frameworks/opt/telephony:/OWNERS
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index edaf8cf279e3..689ff66a3b4d 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5451,6 +5451,8 @@
<bool name="config_supportsHardwareCamToggle">false</bool>
<!-- Whether a camera intent is launched when the lens cover is toggled -->
<bool name="config_launchCameraOnCameraLensCoverToggle">true</bool>
+ <!-- Whether changing sensor privacy SW setting requires device to be unlocked -->
+ <bool name="config_sensorPrivacyRequiresAuthentication">true</bool>
<!-- List containing the allowed install sources for accessibility service. -->
<string-array name="config_accessibility_allowed_install_source" translatable="false"/>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b88212324d24..443f9a628a7e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4661,6 +4661,7 @@
<java-symbol type="bool" name="config_supportsHardwareMicToggle" />
<java-symbol type="bool" name="config_supportsHardwareCamToggle" />
<java-symbol type="bool" name="config_launchCameraOnCameraLensCoverToggle" />
+ <java-symbol type="bool" name="config_sensorPrivacyRequiresAuthentication" />
<java-symbol type="dimen" name="starting_surface_icon_size" />
<java-symbol type="dimen" name="starting_surface_default_icon_size" />
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityLoggerFake.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityLoggerFake.java
index 2ecc26179964..0dca63847269 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityLoggerFake.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityLoggerFake.java
@@ -44,6 +44,7 @@ public class ChooserActivityLoggerFake implements ChooserActivityLogger {
// share completed fields
public int targetType;
public int positionPicked;
+ public boolean isPinned;
CallRecord(int atomId, UiEventLogger.UiEventEnum eventId,
String packageName, InstanceId instanceId) {
@@ -68,12 +69,13 @@ public class ChooserActivityLoggerFake implements ChooserActivityLogger {
}
CallRecord(int atomId, String packageName, InstanceId instanceId, int targetType,
- int positionPicked) {
+ int positionPicked, boolean isPinned) {
this.atomId = atomId;
this.packageName = packageName;
this.instanceId = instanceId;
this.targetType = targetType;
this.positionPicked = positionPicked;
+ this.isPinned = isPinned;
}
}
@@ -112,9 +114,11 @@ public class ChooserActivityLoggerFake implements ChooserActivityLogger {
}
@Override
- public void logShareTargetSelected(int targetType, String packageName, int positionPicked) {
+ public void logShareTargetSelected(int targetType, String packageName, int positionPicked,
+ boolean isPinned) {
mCalls.add(new CallRecord(FrameworkStatsLog.RANKING_SELECTED, packageName, getInstanceId(),
- SharesheetTargetSelectedEvent.fromTargetType(targetType).getId(), positionPicked));
+ SharesheetTargetSelectedEvent.fromTargetType(targetType).getId(), positionPicked,
+ isPinned));
}
@Override
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 891c82d2c166..60da2e8cba27 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -2575,12 +2575,6 @@
"group": "WM_DEBUG_ANIM",
"at": "com\/android\/server\/wm\/WindowContainer.java"
},
- "397105698": {
- "message": "grantEmbeddedWindowFocus remove request for win=%s dropped since no candidate was found",
- "level": "VERBOSE",
- "group": "WM_DEBUG_FOCUS",
- "at": "com\/android\/server\/wm\/WindowManagerService.java"
- },
"397382873": {
"message": "Moving to PAUSED: %s %s",
"level": "VERBOSE",
@@ -3109,6 +3103,12 @@
"group": "WM_DEBUG_LOCKTASK",
"at": "com\/android\/server\/wm\/LockTaskController.java"
},
+ "958338552": {
+ "message": "grantEmbeddedWindowFocus win=%s dropped focus so setting focus to null since no candidate was found",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_FOCUS",
+ "at": "com\/android\/server\/wm\/WindowManagerService.java"
+ },
"959486822": {
"message": "setSyncGroup #%d on %s",
"level": "VERBOSE",
diff --git a/data/keyboards/Vendor_0957_Product_0001.kl b/data/keyboards/Vendor_0957_Product_0001.kl
index 13b4096ca82a..54f8808e2285 100644
--- a/data/keyboards/Vendor_0957_Product_0001.kl
+++ b/data/keyboards/Vendor_0957_Product_0001.kl
@@ -44,33 +44,37 @@ key 10 9
key 11 0
# custom keys
-key usage 0x000c019C PROFILE_SWITCH
-key usage 0x000c01A2 ALL_APPS
key usage 0x000c01BB TV_INPUT
+
+key usage 0x000c0185 TV_TELETEXT
+key usage 0x000c0061 CAPTIONS
+
+key usage 0x000c01BD INFO
+key usage 0x000c0037 PERIOD
+
+key usage 0x000c0069 PROG_RED
+key usage 0x000c006A PROG_GREEN
+key usage 0x000c006C PROG_YELLOW
+key usage 0x000c006B PROG_BLUE
+key usage 0x000c00B4 MEDIA_SKIP_BACKWARD
+key usage 0x000c00CD MEDIA_PLAY_PAUSE
+key usage 0x000c00B2 MEDIA_RECORD
+key usage 0x000c00B3 MEDIA_SKIP_FORWARD
+
key usage 0x000c022A BOOKMARK
+key usage 0x000c01A2 ALL_APPS
+key usage 0x000c019C PROFILE_SWITCH
+
key usage 0x000c0096 SETTINGS
key usage 0x000c009F NOTIFICATION
+
key usage 0x000c008D GUIDE
key usage 0x000c0089 TV
+
key usage 0x000c009C CHANNEL_UP
key usage 0x000c009D CHANNEL_DOWN
-key usage 0x000c00CD MEDIA_PLAY_PAUSE
-key usage 0x000c00B2 MEDIA_RECORD
-key usage 0x000c00B4 MEDIA_SKIP_BACKWARD
-key usage 0x000c00B3 MEDIA_SKIP_FORWARD
-key usage 0x000c0226 MEDIA_STOP
key usage 0x000c0077 BUTTON_3 WAKE #YouTube
key usage 0x000c0078 BUTTON_4 WAKE #Netflix
-key usage 0x000c0079 BUTTON_6 WAKE #Disney+
-key usage 0x000c007A BUTTON_7 WAKE #HBOmax
-
-key usage 0x00070037 PERIOD
-key usage 0x000c01BD INFO
-key usage 0x000c0061 CAPTIONS
-key usage 0x000c0185 TV_TELETEXT
-
-key usage 0x000c0069 PROG_RED
-key usage 0x000c006A PROG_GREEN
-key usage 0x000c006B PROG_BLUE
-key usage 0x000c006C PROG_YELLOW \ No newline at end of file
+key usage 0x000c0079 BUTTON_6 WAKE
+key usage 0x000c007A BUTTON_7 WAKE \ No newline at end of file
diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java
index 4b723d1569c9..1c41d06a3da2 100644
--- a/graphics/java/android/graphics/BLASTBufferQueue.java
+++ b/graphics/java/android/graphics/BLASTBufferQueue.java
@@ -43,6 +43,12 @@ public final class BLASTBufferQueue {
private static native boolean nativeIsSameSurfaceControl(long ptr, long surfaceControlPtr);
private static native SurfaceControl.Transaction nativeGatherPendingTransactions(long ptr,
long frameNumber);
+ private static native void nativeSetTransactionHangCallback(long ptr,
+ TransactionHangCallback callback);
+
+ public interface TransactionHangCallback {
+ void onTransactionHang(boolean isGpuHang);
+ }
/** Create a new connection with the surface flinger. */
public BLASTBufferQueue(String name, SurfaceControl sc, int width, int height,
@@ -184,4 +190,8 @@ public final class BLASTBufferQueue {
public SurfaceControl.Transaction gatherPendingTransactions(long frameNumber) {
return nativeGatherPendingTransactions(mNativeObject, frameNumber);
}
+
+ public void setTransactionHangCallback(TransactionHangCallback hangCallback) {
+ nativeSetTransactionHangCallback(mNativeObject, hangCallback);
+ }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 82f8a131ae2a..faada1aa03ef 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -777,22 +777,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return null;
}
- private void updateCallbackIfNecessary() {
- updateCallbackIfNecessary(true /* deferCallbackUntilAllActivitiesCreated */);
- }
-
/**
* Notifies listeners about changes to split states if necessary.
- *
- * @param deferCallbackUntilAllActivitiesCreated boolean to indicate whether the split info
- * callback should be deferred until all the
- * organized activities have been created.
*/
- private void updateCallbackIfNecessary(boolean deferCallbackUntilAllActivitiesCreated) {
+ private void updateCallbackIfNecessary() {
if (mEmbeddingCallback == null) {
return;
}
- if (deferCallbackUntilAllActivitiesCreated && !allActivitiesCreated()) {
+ if (!allActivitiesCreated()) {
return;
}
List<SplitInfo> currentSplitStates = getActiveSplitStates();
@@ -848,9 +840,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
for (TaskFragmentContainer container : containers) {
- if (container.getInfo() == null
- || container.getInfo().getActivities().size()
- != container.collectActivities().size()) {
+ if (!container.taskInfoActivityCountMatchesCreated()) {
return false;
}
}
@@ -1035,11 +1025,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
&& container.getTaskFragmentToken().equals(initialTaskFragmentToken)) {
// The onTaskFragmentInfoChanged callback containing this activity has not
// reached the client yet, so add the activity to the pending appeared
- // activities and send a split info callback to the client before
- // {@link Activity#onCreate} is called.
+ // activities.
container.addPendingAppearedActivity(activity);
- updateCallbackIfNecessary(
- false /* deferCallbackUntilAllActivitiesCreated */);
return;
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 35981d3af948..26bbcbb937f0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -145,6 +145,18 @@ class TaskFragmentContainer {
return allActivities;
}
+ /**
+ * Checks if the count of activities from the same process in task fragment info corresponds to
+ * the ones created and available on the client side.
+ */
+ boolean taskInfoActivityCountMatchesCreated() {
+ if (mInfo == null) {
+ return false;
+ }
+ return mPendingAppearedActivities.isEmpty()
+ && mInfo.getActivities().size() == collectActivities().size();
+ }
+
ActivityStack toActivityStack() {
return new ActivityStack(collectActivities(), isEmpty());
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index c3fbe5543630..8fa9f564fb22 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -246,9 +246,13 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
* {@link BackAnimationController}
*/
public void onMotionEvent(MotionEvent event, int action, @BackEvent.SwipeEdge int swipeEdge) {
- if (action == MotionEvent.ACTION_DOWN) {
- initAnimation(event);
- } else if (action == MotionEvent.ACTION_MOVE) {
+ if (action == MotionEvent.ACTION_MOVE) {
+ if (!mBackGestureStarted) {
+ // Let the animation initialized here to make sure the onPointerDownOutsideFocus
+ // could be happened when ACTION_DOWN, it may change the current focus that we
+ // would access it when startBackNavigation.
+ initAnimation(event);
+ }
onMove(event, swipeEdge);
} else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
ProtoLog.d(WM_SHELL_BACK_PREVIEW,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 227494c04049..31fc6a5be589 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -71,7 +71,7 @@ public class Bubble implements BubbleViewProvider {
private long mLastAccessed;
@Nullable
- private Bubbles.SuppressionChangedListener mSuppressionListener;
+ private Bubbles.BubbleMetadataFlagListener mBubbleMetadataFlagListener;
/** Whether the bubble should show a dot for the notification indicating updated content. */
private boolean mShowBubbleUpdateDot = true;
@@ -192,13 +192,13 @@ public class Bubble implements BubbleViewProvider {
@VisibleForTesting(visibility = PRIVATE)
public Bubble(@NonNull final BubbleEntry entry,
- @Nullable final Bubbles.SuppressionChangedListener listener,
+ @Nullable final Bubbles.BubbleMetadataFlagListener listener,
final Bubbles.PendingIntentCanceledListener intentCancelListener,
Executor mainExecutor) {
mKey = entry.getKey();
mGroupKey = entry.getGroupKey();
mLocusId = entry.getLocusId();
- mSuppressionListener = listener;
+ mBubbleMetadataFlagListener = listener;
mIntentCancelListener = intent -> {
if (mIntent != null) {
mIntent.unregisterCancelListener(mIntentCancelListener);
@@ -606,8 +606,8 @@ public class Bubble implements BubbleViewProvider {
mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
}
- if (showInShade() != prevShowInShade && mSuppressionListener != null) {
- mSuppressionListener.onBubbleNotificationSuppressionChange(this);
+ if (showInShade() != prevShowInShade && mBubbleMetadataFlagListener != null) {
+ mBubbleMetadataFlagListener.onBubbleMetadataFlagChanged(this);
}
}
@@ -626,8 +626,8 @@ public class Bubble implements BubbleViewProvider {
} else {
mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE;
}
- if (prevSuppressed != suppressBubble && mSuppressionListener != null) {
- mSuppressionListener.onBubbleNotificationSuppressionChange(this);
+ if (prevSuppressed != suppressBubble && mBubbleMetadataFlagListener != null) {
+ mBubbleMetadataFlagListener.onBubbleMetadataFlagChanged(this);
}
}
@@ -771,12 +771,17 @@ public class Bubble implements BubbleViewProvider {
return isEnabled(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
}
- void setShouldAutoExpand(boolean shouldAutoExpand) {
+ @VisibleForTesting
+ public void setShouldAutoExpand(boolean shouldAutoExpand) {
+ boolean prevAutoExpand = shouldAutoExpand();
if (shouldAutoExpand) {
enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
} else {
disable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
}
+ if (prevAutoExpand != shouldAutoExpand && mBubbleMetadataFlagListener != null) {
+ mBubbleMetadataFlagListener.onBubbleMetadataFlagChanged(this);
+ }
}
public void setIsBubble(final boolean isBubble) {
@@ -799,6 +804,10 @@ public class Bubble implements BubbleViewProvider {
return (mFlags & option) != 0;
}
+ public int getFlags() {
+ return mFlags;
+ }
+
@Override
public String toString() {
return "Bubble{" + mKey + '}';
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 806c395bf395..f407bdcb8852 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -323,7 +323,7 @@ public class BubbleController {
public void initialize() {
mBubbleData.setListener(mBubbleDataListener);
- mBubbleData.setSuppressionChangedListener(this::onBubbleNotificationSuppressionChanged);
+ mBubbleData.setSuppressionChangedListener(this::onBubbleMetadataFlagChanged);
mBubbleData.setPendingIntentCancelledListener(bubble -> {
if (bubble.getBubbleIntent() == null) {
@@ -554,11 +554,10 @@ public class BubbleController {
}
@VisibleForTesting
- public void onBubbleNotificationSuppressionChanged(Bubble bubble) {
+ public void onBubbleMetadataFlagChanged(Bubble bubble) {
// Make sure NoMan knows suppression state so that anyone querying it can tell.
try {
- mBarService.onBubbleNotificationSuppressionChanged(bubble.getKey(),
- !bubble.showInShade(), bubble.isSuppressed());
+ mBarService.onBubbleMetadataFlagChanged(bubble.getKey(), bubble.getFlags());
} catch (RemoteException e) {
// Bad things have happened
}
@@ -1038,7 +1037,15 @@ public class BubbleController {
}
} else {
Bubble bubble = mBubbleData.getOrCreateBubble(notif, null /* persistedBubble */);
- inflateAndAdd(bubble, suppressFlyout, showInShade);
+ if (notif.shouldSuppressNotificationList()) {
+ // If we're suppressing notifs for DND, we don't want the bubbles to randomly
+ // expand when DND turns off so flip the flag.
+ if (bubble.shouldAutoExpand()) {
+ bubble.setShouldAutoExpand(false);
+ }
+ } else {
+ inflateAndAdd(bubble, suppressFlyout, showInShade);
+ }
}
}
@@ -1070,7 +1077,8 @@ public class BubbleController {
}
}
- private void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp) {
+ @VisibleForTesting
+ public void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp) {
// shouldBubbleUp checks canBubble & for bubble metadata
boolean shouldBubble = shouldBubbleUp && canLaunchInTaskView(mContext, entry);
if (!shouldBubble && mBubbleData.hasAnyBubbleWithKey(entry.getKey())) {
@@ -1096,7 +1104,8 @@ public class BubbleController {
}
}
- private void onRankingUpdated(RankingMap rankingMap,
+ @VisibleForTesting
+ public void onRankingUpdated(RankingMap rankingMap,
HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey) {
if (mTmpRanking == null) {
mTmpRanking = new NotificationListenerService.Ranking();
@@ -1107,19 +1116,22 @@ public class BubbleController {
Pair<BubbleEntry, Boolean> entryData = entryDataByKey.get(key);
BubbleEntry entry = entryData.first;
boolean shouldBubbleUp = entryData.second;
-
if (entry != null && !isCurrentProfile(
entry.getStatusBarNotification().getUser().getIdentifier())) {
return;
}
-
+ if (entry != null && (entry.shouldSuppressNotificationList()
+ || entry.getRanking().isSuspended())) {
+ shouldBubbleUp = false;
+ }
rankingMap.getRanking(key, mTmpRanking);
- boolean isActiveBubble = mBubbleData.hasAnyBubbleWithKey(key);
- if (isActiveBubble && !mTmpRanking.canBubble()) {
+ boolean isActiveOrInOverflow = mBubbleData.hasAnyBubbleWithKey(key);
+ boolean isActive = mBubbleData.hasBubbleInStackWithKey(key);
+ if (isActiveOrInOverflow && !mTmpRanking.canBubble()) {
// If this entry is no longer allowed to bubble, dismiss with the BLOCKED reason.
// This means that the app or channel's ability to bubble has been revoked.
mBubbleData.dismissBubbleWithKey(key, DISMISS_BLOCKED);
- } else if (isActiveBubble && (!shouldBubbleUp || entry.getRanking().isSuspended())) {
+ } else if (isActiveOrInOverflow && !shouldBubbleUp) {
// If this entry is allowed to bubble, but cannot currently bubble up or is
// suspended, dismiss it. This happens when DND is enabled and configured to hide
// bubbles, or focus mode is enabled and the app is designated as distracting.
@@ -1127,9 +1139,9 @@ public class BubbleController {
// notification, so that the bubble will be re-created if shouldBubbleUp returns
// true.
mBubbleData.dismissBubbleWithKey(key, DISMISS_NO_BUBBLE_UP);
- } else if (entry != null && mTmpRanking.isBubble() && !isActiveBubble) {
+ } else if (entry != null && mTmpRanking.isBubble() && !isActive) {
entry.setFlagBubble(true);
- onEntryUpdated(entry, shouldBubbleUp && !entry.getRanking().isSuspended());
+ onEntryUpdated(entry, shouldBubbleUp);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index c98c0e69de15..e4a0fd03860c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -159,7 +159,7 @@ public class BubbleData {
private Listener mListener;
@Nullable
- private Bubbles.SuppressionChangedListener mSuppressionListener;
+ private Bubbles.BubbleMetadataFlagListener mBubbleMetadataFlagListener;
private Bubbles.PendingIntentCanceledListener mCancelledListener;
/**
@@ -190,9 +190,8 @@ public class BubbleData {
mMaxOverflowBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_overflow);
}
- public void setSuppressionChangedListener(
- Bubbles.SuppressionChangedListener listener) {
- mSuppressionListener = listener;
+ public void setSuppressionChangedListener(Bubbles.BubbleMetadataFlagListener listener) {
+ mBubbleMetadataFlagListener = listener;
}
public void setPendingIntentCancelledListener(
@@ -311,7 +310,7 @@ public class BubbleData {
bubbleToReturn = mPendingBubbles.get(key);
} else if (entry != null) {
// New bubble
- bubbleToReturn = new Bubble(entry, mSuppressionListener, mCancelledListener,
+ bubbleToReturn = new Bubble(entry, mBubbleMetadataFlagListener, mCancelledListener,
mMainExecutor);
} else {
// Persisted bubble being promoted
@@ -1058,6 +1057,22 @@ public class BubbleData {
return null;
}
+ /**
+ * Get a pending bubble with given notification <code>key</code>
+ *
+ * @param key notification key
+ * @return bubble that matches or null
+ */
+ @VisibleForTesting(visibility = PRIVATE)
+ public Bubble getPendingBubbleWithKey(String key) {
+ for (Bubble b : mPendingBubbles.values()) {
+ if (b.getKey().equals(key)) {
+ return b;
+ }
+ }
+ return null;
+ }
+
@VisibleForTesting(visibility = PRIVATE)
void setTimeSource(TimeSource timeSource) {
mTimeSource = timeSource;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 2b2a2f7e35df..c7db8d8d1646 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -263,10 +263,10 @@ public interface Bubbles {
void onBubbleExpandChanged(boolean isExpanding, String key);
}
- /** Listener to be notified when the flags for notification or bubble suppression changes.*/
- interface SuppressionChangedListener {
- /** Called when the notification suppression state of a bubble changes. */
- void onBubbleNotificationSuppressionChange(Bubble bubble);
+ /** Listener to be notified when the flags on BubbleMetadata have changed. */
+ interface BubbleMetadataFlagListener {
+ /** Called when the flags on BubbleMetadata have changed for the provided bubble. */
+ void onBubbleMetadataFlagChanged(Bubble bubble);
}
/** Listener to be notified when a pending intent has been canceled for a bubble. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 4b125b118ceb..6305959bb6ac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -24,6 +24,7 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Rect;
+import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Property;
import android.view.GestureDetector;
@@ -37,6 +38,8 @@ import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
@@ -80,7 +83,6 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
private final Rect mTempRect = new Rect();
private FrameLayout mDividerBar;
-
static final Property<DividerView, Integer> DIVIDER_HEIGHT_PROPERTY =
new Property<DividerView, Integer>(Integer.class, "height") {
@Override
@@ -109,6 +111,74 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
}
};
+ private final AccessibilityDelegate mHandleDelegate = new AccessibilityDelegate() {
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ final DividerSnapAlgorithm snapAlgorithm = mSplitLayout.mDividerSnapAlgorithm;
+ if (isLandscape()) {
+ info.addAction(new AccessibilityAction(R.id.action_move_tl_full,
+ mContext.getString(R.string.accessibility_action_divider_left_full)));
+ if (snapAlgorithm.isFirstSplitTargetAvailable()) {
+ info.addAction(new AccessibilityAction(R.id.action_move_tl_70,
+ mContext.getString(R.string.accessibility_action_divider_left_70)));
+ }
+ if (snapAlgorithm.showMiddleSplitTargetForAccessibility()) {
+ // Only show the middle target if there are more than 1 split target
+ info.addAction(new AccessibilityAction(R.id.action_move_tl_50,
+ mContext.getString(R.string.accessibility_action_divider_left_50)));
+ }
+ if (snapAlgorithm.isLastSplitTargetAvailable()) {
+ info.addAction(new AccessibilityAction(R.id.action_move_tl_30,
+ mContext.getString(R.string.accessibility_action_divider_left_30)));
+ }
+ info.addAction(new AccessibilityAction(R.id.action_move_rb_full,
+ mContext.getString(R.string.accessibility_action_divider_right_full)));
+ } else {
+ info.addAction(new AccessibilityAction(R.id.action_move_tl_full,
+ mContext.getString(R.string.accessibility_action_divider_top_full)));
+ if (snapAlgorithm.isFirstSplitTargetAvailable()) {
+ info.addAction(new AccessibilityAction(R.id.action_move_tl_70,
+ mContext.getString(R.string.accessibility_action_divider_top_70)));
+ }
+ if (snapAlgorithm.showMiddleSplitTargetForAccessibility()) {
+ // Only show the middle target if there are more than 1 split target
+ info.addAction(new AccessibilityAction(R.id.action_move_tl_50,
+ mContext.getString(R.string.accessibility_action_divider_top_50)));
+ }
+ if (snapAlgorithm.isLastSplitTargetAvailable()) {
+ info.addAction(new AccessibilityAction(R.id.action_move_tl_30,
+ mContext.getString(R.string.accessibility_action_divider_top_30)));
+ }
+ info.addAction(new AccessibilityAction(R.id.action_move_rb_full,
+ mContext.getString(R.string.accessibility_action_divider_bottom_full)));
+ }
+ }
+
+ @Override
+ public boolean performAccessibilityAction(@NonNull View host, int action,
+ @Nullable Bundle args) {
+ DividerSnapAlgorithm.SnapTarget nextTarget = null;
+ DividerSnapAlgorithm snapAlgorithm = mSplitLayout.mDividerSnapAlgorithm;
+ if (action == R.id.action_move_tl_full) {
+ nextTarget = snapAlgorithm.getDismissEndTarget();
+ } else if (action == R.id.action_move_tl_70) {
+ nextTarget = snapAlgorithm.getLastSplitTarget();
+ } else if (action == R.id.action_move_tl_50) {
+ nextTarget = snapAlgorithm.getMiddleTarget();
+ } else if (action == R.id.action_move_tl_30) {
+ nextTarget = snapAlgorithm.getFirstSplitTarget();
+ } else if (action == R.id.action_move_rb_full) {
+ nextTarget = snapAlgorithm.getDismissStartTarget();
+ }
+ if (nextTarget != null) {
+ mSplitLayout.snapToTarget(mSplitLayout.getDividePosition(), nextTarget);
+ return true;
+ }
+ return super.performAccessibilityAction(host, action, args);
+ }
+ };
+
public DividerView(@NonNull Context context) {
super(context);
}
@@ -179,6 +249,7 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
mDoubleTapDetector = new GestureDetector(getContext(), new DoubleTapListener());
mInteractive = true;
setOnTouchListener(this);
+ mHandle.setAccessibilityDelegate(mHandleDelegate);
}
@Override
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 72c8141c8f2a..dfd4362e2373 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
@@ -29,6 +29,7 @@ import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
+import com.android.wm.shell.pip.PipAppOpsListener;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSnapAlgorithm;
@@ -63,6 +64,7 @@ public abstract class TvPipModule {
Context context,
TvPipBoundsState tvPipBoundsState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
+ PipAppOpsListener pipAppOpsListener,
PipTaskOrganizer pipTaskOrganizer,
TvPipMenuController tvPipMenuController,
PipMediaController pipMediaController,
@@ -79,6 +81,7 @@ public abstract class TvPipModule {
context,
tvPipBoundsState,
tvPipBoundsAlgorithm,
+ pipAppOpsListener,
pipTaskOrganizer,
pipTransitionController,
tvPipMenuController,
@@ -140,8 +143,11 @@ public abstract class TvPipModule {
@Provides
static TvPipNotificationController provideTvPipNotificationController(Context context,
PipMediaController pipMediaController,
+ PipParamsChangedForwarder pipParamsChangedForwarder,
+ TvPipBoundsState tvPipBoundsState,
@ShellMainThread Handler mainHandler) {
- return new TvPipNotificationController(context, pipMediaController, mainHandler);
+ return new TvPipNotificationController(context, pipMediaController,
+ pipParamsChangedForwarder, tvPipBoundsState, mainHandler);
}
@WMSingleton
@@ -185,4 +191,12 @@ public abstract class TvPipModule {
static PipParamsChangedForwarder providePipParamsChangedForwarder() {
return new PipParamsChangedForwarder();
}
+
+ @WMSingleton
+ @Provides
+ static PipAppOpsListener providePipAppOpsListener(Context context,
+ PipTaskOrganizer pipTaskOrganizer,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new PipAppOpsListener(context, pipTaskOrganizer::removePip, mainExecutor);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 333567320312..db6131a17114 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -78,7 +78,6 @@ import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipUiEventLogger;
-import com.android.wm.shell.pip.phone.PipAppOpsListener;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.recents.RecentTasks;
import com.android.wm.shell.recents.RecentTasksController;
@@ -435,14 +434,6 @@ public abstract class WMShellBaseModule {
return new FloatingContentCoordinator();
}
- @WMSingleton
- @Provides
- static PipAppOpsListener providePipAppOpsListener(Context context,
- PipTouchHandler pipTouchHandler,
- @ShellMainThread ShellExecutor mainExecutor) {
- return new PipAppOpsListener(context, pipTouchHandler.getMotionHelper(), mainExecutor);
- }
-
// Needs handler for registering broadcast receivers
@WMSingleton
@Provides
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 7513e5129ade..1bc9e31b9e2e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -51,6 +51,7 @@ import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
+import com.android.wm.shell.pip.PipAppOpsListener;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMediaController;
@@ -63,9 +64,7 @@ import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.phone.PhonePipMenuController;
-import com.android.wm.shell.pip.phone.PipAppOpsListener;
import com.android.wm.shell.pip.phone.PipController;
-import com.android.wm.shell.pip.phone.PipKeepClearAlgorithm;
import com.android.wm.shell.pip.phone.PipMotionHelper;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.recents.RecentTasksController;
@@ -211,8 +210,7 @@ public class WMShellModule {
@Provides
static Optional<Pip> providePip(Context context, DisplayController displayController,
PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm,
- PipKeepClearAlgorithm pipKeepClearAlgorithm, PipBoundsState pipBoundsState,
- PipMotionHelper pipMotionHelper, PipMediaController pipMediaController,
+ PipBoundsState pipBoundsState, PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer,
PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController,
WindowManagerShellWrapper windowManagerShellWrapper,
@@ -221,8 +219,8 @@ public class WMShellModule {
Optional<OneHandedController> oneHandedController,
@ShellMainThread ShellExecutor mainExecutor) {
return Optional.ofNullable(PipController.create(context, displayController,
- pipAppOpsListener, pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState,
- pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer,
+ pipAppOpsListener, pipBoundsAlgorithm, pipBoundsState,
+ pipMediaController, phonePipMenuController, pipTaskOrganizer,
pipTouchHandler, pipTransitionController, windowManagerShellWrapper,
taskStackListener, pipParamsChangedForwarder, oneHandedController, mainExecutor));
}
@@ -241,12 +239,6 @@ public class WMShellModule {
@WMSingleton
@Provides
- static PipKeepClearAlgorithm providePipKeepClearAlgorithm() {
- return new PipKeepClearAlgorithm();
- }
-
- @WMSingleton
- @Provides
static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context,
PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm) {
return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm);
@@ -333,6 +325,14 @@ public class WMShellModule {
@WMSingleton
@Provides
+ static PipAppOpsListener providePipAppOpsListener(Context context,
+ PipTouchHandler pipTouchHandler,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new PipAppOpsListener(context, pipTouchHandler.getMotionHelper(), mainExecutor);
+ }
+
+ @WMSingleton
+ @Provides
static PipMotionHelper providePipMotionHelper(Context context,
PipBoundsState pipBoundsState, PipTaskOrganizer pipTaskOrganizer,
PhonePipMenuController menuController, PipSnapAlgorithm pipSnapAlgorithm,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAppOpsListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAppOpsListener.java
index d97d2d6ebb4f..48a3fc2460a2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAppOpsListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAppOpsListener.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip.phone;
+package com.android.wm.shell.pip;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE;
@@ -28,7 +28,6 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.util.Pair;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.pip.PipUtils;
public class PipAppOpsListener {
private static final String TAG = PipAppOpsListener.class.getSimpleName();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
index 8a50f2233573..65a12d629c5a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
@@ -32,6 +32,7 @@ import android.content.IntentFilter;
import android.graphics.drawable.Icon;
import android.media.MediaMetadata;
import android.media.session.MediaController;
+import android.media.session.MediaSession;
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
import android.os.Handler;
@@ -64,7 +65,7 @@ public class PipMediaController {
*/
public interface ActionListener {
/**
- * Called when the media actions changes.
+ * Called when the media actions changed.
*/
void onMediaActionsChanged(List<RemoteAction> actions);
}
@@ -74,11 +75,21 @@ public class PipMediaController {
*/
public interface MetadataListener {
/**
- * Called when the media metadata changes.
+ * Called when the media metadata changed.
*/
void onMediaMetadataChanged(MediaMetadata metadata);
}
+ /**
+ * A listener interface to receive notification on changes to the media session token.
+ */
+ public interface TokenListener {
+ /**
+ * Called when the media session token changed.
+ */
+ void onMediaSessionTokenChanged(MediaSession.Token token);
+ }
+
private final Context mContext;
private final Handler mMainHandler;
private final HandlerExecutor mHandlerExecutor;
@@ -133,6 +144,7 @@ public class PipMediaController {
private final ArrayList<ActionListener> mActionListeners = new ArrayList<>();
private final ArrayList<MetadataListener> mMetadataListeners = new ArrayList<>();
+ private final ArrayList<TokenListener> mTokenListeners = new ArrayList<>();
public PipMediaController(Context context, Handler mainHandler) {
mContext = context;
@@ -204,6 +216,31 @@ public class PipMediaController {
mMetadataListeners.remove(listener);
}
+ /**
+ * Adds a new token listener.
+ */
+ public void addTokenListener(TokenListener listener) {
+ if (!mTokenListeners.contains(listener)) {
+ mTokenListeners.add(listener);
+ listener.onMediaSessionTokenChanged(getToken());
+ }
+ }
+
+ /**
+ * Removes a token listener.
+ */
+ public void removeTokenListener(TokenListener listener) {
+ listener.onMediaSessionTokenChanged(null);
+ mTokenListeners.remove(listener);
+ }
+
+ private MediaSession.Token getToken() {
+ if (mMediaController == null) {
+ return null;
+ }
+ return mMediaController.getSessionToken();
+ }
+
private MediaMetadata getMediaMetadata() {
return mMediaController != null ? mMediaController.getMetadata() : null;
}
@@ -294,6 +331,7 @@ public class PipMediaController {
}
notifyActionsChanged();
notifyMetadataChanged(getMediaMetadata());
+ notifyTokenChanged(getToken());
// TODO(winsonc): Consider if we want to close the PIP after a timeout (like on TV)
}
@@ -317,4 +355,10 @@ public class PipMediaController {
mMetadataListeners.forEach(l -> l.onMediaMetadataChanged(metadata));
}
}
+
+ private void notifyTokenChanged(MediaSession.Token token) {
+ if (!mTokenListeners.isEmpty()) {
+ mTokenListeners.forEach(l -> l.onMediaSessionTokenChanged(token));
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 2e8b5b7979d0..42ceb42f39e8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -76,6 +76,7 @@ import com.android.wm.shell.pip.IPipAnimationListener;
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
+import com.android.wm.shell.pip.PipAppOpsListener;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMediaController;
@@ -109,9 +110,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
private PipAppOpsListener mAppOpsListener;
private PipMediaController mMediaController;
private PipBoundsAlgorithm mPipBoundsAlgorithm;
- private PipKeepClearAlgorithm mPipKeepClearAlgorithm;
private PipBoundsState mPipBoundsState;
- private PipMotionHelper mPipMotionHelper;
private PipTouchHandler mTouchHandler;
private PipTransitionController mPipTransitionController;
private TaskStackListenerImpl mTaskStackListener;
@@ -247,10 +246,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb
Set<Rect> unrestricted) {
if (mPipBoundsState.getDisplayId() == displayId) {
mPipBoundsState.setKeepClearAreas(restricted, unrestricted);
- mPipMotionHelper.moveToBounds(mPipKeepClearAlgorithm.adjust(
- mPipBoundsState.getBounds(),
- mPipBoundsState.getRestrictedKeepClearAreas(),
- mPipBoundsState.getUnrestrictedKeepClearAreas()));
}
}
};
@@ -289,8 +284,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
@Nullable
public static Pip create(Context context, DisplayController displayController,
PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm,
- PipKeepClearAlgorithm pipKeepClearAlgorithm, PipBoundsState pipBoundsState,
- PipMotionHelper pipMotionHelper, PipMediaController pipMediaController,
+ PipBoundsState pipBoundsState, PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer,
PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController,
WindowManagerShellWrapper windowManagerShellWrapper,
@@ -305,7 +299,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
return new PipController(context, displayController, pipAppOpsListener, pipBoundsAlgorithm,
- pipKeepClearAlgorithm, pipBoundsState, pipMotionHelper, pipMediaController,
+ pipBoundsState, pipMediaController,
phonePipMenuController, pipTaskOrganizer, pipTouchHandler, pipTransitionController,
windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
oneHandedController, mainExecutor)
@@ -316,9 +310,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
DisplayController displayController,
PipAppOpsListener pipAppOpsListener,
PipBoundsAlgorithm pipBoundsAlgorithm,
- PipKeepClearAlgorithm pipKeepClearAlgorithm,
@NonNull PipBoundsState pipBoundsState,
- PipMotionHelper pipMotionHelper,
PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController,
PipTaskOrganizer pipTaskOrganizer,
@@ -341,9 +333,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mWindowManagerShellWrapper = windowManagerShellWrapper;
mDisplayController = displayController;
mPipBoundsAlgorithm = pipBoundsAlgorithm;
- mPipKeepClearAlgorithm = pipKeepClearAlgorithm;
mPipBoundsState = pipBoundsState;
- mPipMotionHelper = pipMotionHelper;
mPipTaskOrganizer = pipTaskOrganizer;
mMainExecutor = mainExecutor;
mMediaController = pipMediaController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java
deleted file mode 100644
index a83258f9063b..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java
+++ /dev/null
@@ -1,44 +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.wm.shell.pip.phone;
-
-import android.graphics.Rect;
-
-import java.util.Set;
-
-/**
- * Calculates the adjusted position that does not occlude keep clear areas.
- */
-public class PipKeepClearAlgorithm {
-
- /** Returns a new {@code Rect} that does not occlude the provided keep clear areas. */
- public Rect adjust(Rect defaultBounds, Set<Rect> restrictedKeepClearAreas,
- Set<Rect> unrestrictedKeepClearAreas) {
- if (restrictedKeepClearAreas.isEmpty()) {
- return defaultBounds;
- }
- // TODO(b/183746978): implement the adjustment algorithm
- // naively check if areas intersect, an if so move PiP upwards
- Rect outBounds = new Rect(defaultBounds);
- for (Rect r : restrictedKeepClearAreas) {
- if (r.intersect(outBounds)) {
- outBounds.offset(0, r.top - outBounds.bottom);
- }
- }
- return outBounds;
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index e9b6babfc5fa..5a21e0734277 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -44,6 +44,7 @@ import com.android.wm.shell.animation.FloatProperties;
import com.android.wm.shell.animation.PhysicsAnimator;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
+import com.android.wm.shell.pip.PipAppOpsListener;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
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 8326588bbbad..766779413094 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
@@ -45,6 +45,7 @@ import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
+import com.android.wm.shell.pip.PipAppOpsListener;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
@@ -97,6 +98,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
private final TvPipBoundsState mTvPipBoundsState;
private final TvPipBoundsAlgorithm mTvPipBoundsAlgorithm;
+ private final PipAppOpsListener mAppOpsListener;
private final PipTaskOrganizer mPipTaskOrganizer;
private final PipMediaController mPipMediaController;
private final TvPipNotificationController mPipNotificationController;
@@ -121,6 +123,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
Context context,
TvPipBoundsState tvPipBoundsState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
+ PipAppOpsListener pipAppOpsListener,
PipTaskOrganizer pipTaskOrganizer,
PipTransitionController pipTransitionController,
TvPipMenuController tvPipMenuController,
@@ -136,6 +139,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
context,
tvPipBoundsState,
tvPipBoundsAlgorithm,
+ pipAppOpsListener,
pipTaskOrganizer,
pipTransitionController,
tvPipMenuController,
@@ -153,6 +157,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
Context context,
TvPipBoundsState tvPipBoundsState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
+ PipAppOpsListener pipAppOpsListener,
PipTaskOrganizer pipTaskOrganizer,
PipTransitionController pipTransitionController,
TvPipMenuController tvPipMenuController,
@@ -181,6 +186,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
mTvPipMenuController = tvPipMenuController;
mTvPipMenuController.setDelegate(this);
+ mAppOpsListener = pipAppOpsListener;
mPipTaskOrganizer = pipTaskOrganizer;
pipTransitionController.registerPipTransitionCallback(this);
@@ -287,6 +293,8 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
}
mTvPipBoundsState.setTvPipManuallyCollapsed(!expanding);
mTvPipBoundsState.setTvPipExpanded(expanding);
+ mPipNotificationController.updateExpansionState();
+
updatePinnedStackBounds();
}
@@ -521,6 +529,12 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
@Override
public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
checkIfPinnedTaskAppeared();
+ mAppOpsListener.onActivityPinned(packageName);
+ }
+
+ @Override
+ public void onActivityUnpinned() {
+ mAppOpsListener.onActivityUnpinned();
}
@Override
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 868e45655ba3..320c05c4a415 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
@@ -494,6 +494,14 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
setFrameHighlighted(false);
}
+ @Override
+ public void onWindowFocusChanged(boolean hasWindowFocus) {
+ super.onWindowFocusChanged(hasWindowFocus);
+ if (!hasWindowFocus) {
+ hideAllUserControls();
+ }
+ }
+
private void animateAlphaTo(float alpha, View view) {
if (view.getAlpha() == alpha) {
return;
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 4033f030b702..61a609d9755e 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,36 +16,47 @@
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.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;
-import android.media.MediaMetadata;
+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;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.util.ImageUtils;
import com.android.wm.shell.R;
import com.android.wm.shell.pip.PipMediaController;
+import com.android.wm.shell.pip.PipParamsChangedForwarder;
+import com.android.wm.shell.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import java.util.Objects;
+import java.util.ArrayList;
+import java.util.List;
/**
- * A notification that informs users that PIP is running and also provides PIP controls.
- * <p>Once it's created, it will manage the PIP notification UI by itself except for handling
- * configuration changes.
+ * A notification that informs users that PiP is running and also provides PiP controls.
+ * <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";
- private static final boolean DEBUG = TvPipController.DEBUG;
// Referenced in com.android.systemui.util.NotificationChannels.
public static final String NOTIFICATION_CHANNEL = "TVPIP";
@@ -60,6 +71,8 @@ public class TvPipNotificationController {
"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;
@@ -68,44 +81,88 @@ public class TvPipNotificationController {
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 MediaSession.Token mMediaSessionToken;
+
/** Package name for the application that owns PiP window. */
private String mPackageName;
- private boolean mNotified;
- private String mMediaTitle;
- private Bitmap mArt;
+
+ private boolean mIsNotificationShown;
+ private String mPipTitle;
+ private String mPipSubtitle;
+
+ private Bitmap mActivityIcon;
public TvPipNotificationController(Context context, PipMediaController pipMediaController,
+ PipParamsChangedForwarder pipParamsChangedForwarder, TvPipBoundsState tvPipBoundsState,
Handler mainHandler) {
mContext = context;
mPackageManager = context.getPackageManager();
mNotificationManager = context.getSystemService(NotificationManager.class);
mMainHandler = mainHandler;
+ mTvPipBoundsState = tvPipBoundsState;
mNotificationBuilder = new Notification.Builder(context, NOTIFICATION_CHANNEL)
.setLocalOnly(true)
- .setOngoing(false)
+ .setOngoing(true)
.setCategory(Notification.CATEGORY_SYSTEM)
.setShowWhen(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();
- pipMediaController.addMetadataListener(this::onMediaMetadataChanged);
+ 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();
+ }
+
+ @Override
+ public void onSubtitleChanged(String subtitle) {
+ mPipSubtitle = subtitle;
+ updateNotificationContent();
+ }
+ });
onConfigurationChanged(context);
}
void setDelegate(Delegate delegate) {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: setDelegate(), delegate=%s", TAG, 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.");
@@ -118,90 +175,181 @@ public class TvPipNotificationController {
}
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.");
}
+ mIsNotificationShown = true;
mPackageName = packageName;
- update();
+ mActivityIcon = getActivityIcon();
mActionBroadcastReceiver.register();
+
+ updateNotificationContent();
}
void dismiss() {
- mNotificationManager.cancel(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP);
- mNotified = false;
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: dismiss()", TAG);
+
+ mIsNotificationShown = false;
mPackageName = null;
mActionBroadcastReceiver.unregister();
+
+ mNotificationManager.cancel(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP);
}
- private void onMediaMetadataChanged(MediaMetadata metadata) {
- if (updateMediaControllerMetadata(metadata) && mNotified) {
- // update notification
- update();
+ 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);
}
}
- /**
- * Called by {@link PipController} when the configuration is changed.
- */
- void onConfigurationChanged(Context context) {
- mDefaultTitle = context.getResources().getString(R.string.pip_notification_unknown_title);
- if (mNotified) {
- // Update the notification.
- update();
+ 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 update() {
- mNotified = true;
- mNotificationBuilder
- .setWhen(System.currentTimeMillis())
- .setContentTitle(getNotificationTitle());
- if (mArt != null) {
- mNotificationBuilder.setStyle(new Notification.BigPictureStyle()
- .bigPicture(mArt));
- } else {
- mNotificationBuilder.setStyle(null);
+ 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);
}
- mNotificationManager.notify(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP,
- mNotificationBuilder.build());
+ builder.setSemanticAction(semanticAction);
+ builder.setContextual(true);
+ return builder.build();
}
- private boolean updateMediaControllerMetadata(MediaMetadata metadata) {
- String title = null;
- Bitmap art = null;
- if (metadata != null) {
- title = metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE);
- if (TextUtils.isEmpty(title)) {
- title = metadata.getString(MediaMetadata.METADATA_KEY_TITLE);
- }
- art = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
- if (art == null) {
- art = metadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
+ 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]);
+ }
- if (TextUtils.equals(title, mMediaTitle) && Objects.equals(art, mArt)) {
- return false;
+ 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);
+ }
- mMediaTitle = title;
- mArt = art;
+ private Notification.Action getMoveAction() {
+ return createSystemAction(R.drawable.pip_ic_move_white, R.string.pip_move,
+ ACTION_MOVE_PIP);
+ }
- return true;
+ /**
+ * 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 String getNotificationTitle() {
- if (!TextUtils.isEmpty(mMediaTitle)) {
- return mMediaTitle;
+ 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());
}
+ mNotificationBuilder
+ .setWhen(System.currentTimeMillis())
+ .setContentTitle(getNotificationTitle())
+ .setContentText(mPipSubtitle)
+ .setSubText(getApplicationLabel(mPackageName))
+ .setActions(actions);
+ setPipIcon();
+
+ Bundle extras = new Bundle();
+ extras.putParcelable(Notification.EXTRA_MEDIA_SESSION, mMediaSessionToken);
+ mNotificationBuilder.setExtras(extras);
+
+ // 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)));
+ mNotificationManager.notify(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP,
+ mNotificationBuilder.build());
+ }
+
+ private String getNotificationTitle() {
+ if (!TextUtils.isEmpty(mPipTitle)) {
+ return mPipTitle;
+ }
final String applicationTitle = getApplicationLabel(mPackageName);
if (!TextUtils.isEmpty(applicationTitle)) {
return applicationTitle;
}
-
return mDefaultTitle;
}
@@ -214,10 +362,37 @@ public class TvPipNotificationController {
}
}
+ private void setPipIcon() {
+ if (mActivityIcon != null) {
+ mNotificationBuilder.setLargeIcon(mActivityIcon);
+ return;
+ }
+ // Fallback: Picture-in-Picture icon
+ mNotificationBuilder.setLargeIcon(Icon.createWithResource(mContext, R.drawable.pip_icon));
+ }
+
+ private Bitmap getActivityIcon() {
+ if (mContext == null) return null;
+ ComponentName componentName = PipUtils.getTopPipActivity(mContext).first;
+ if (componentName == null) return null;
+
+ Drawable drawable;
+ try {
+ drawable = mPackageManager.getActivityIcon(componentName);
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ }
+ int width = mContext.getResources().getDimensionPixelSize(
+ android.R.dimen.notification_large_icon_width);
+ int height = mContext.getResources().getDimensionPixelSize(
+ android.R.dimen.notification_large_icon_height);
+ return ImageUtils.buildScaledBitmap(drawable, width, height, /* allowUpscaling */ true);
+ }
+
private static PendingIntent createPendingIntent(Context context, String action) {
return PendingIntent.getBroadcast(context, 0,
new Intent(action).setPackage(context.getPackageName()),
- PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
}
private class ActionBroadcastReceiver extends BroadcastReceiver {
@@ -228,6 +403,7 @@ public class TvPipNotificationController {
mIntentFilter.addAction(ACTION_SHOW_PIP_MENU);
mIntentFilter.addAction(ACTION_MOVE_PIP);
mIntentFilter.addAction(ACTION_TOGGLE_EXPANDED_PIP);
+ mIntentFilter.addAction(ACTION_FULLSCREEN);
}
boolean mRegistered = false;
@@ -249,10 +425,8 @@ public class TvPipNotificationController {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: on(Broadcast)Receive(), action=%s", TAG, action);
- }
+ 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();
@@ -262,14 +436,21 @@ public class TvPipNotificationController {
mDelegate.enterPipMovementMenu();
} else if (ACTION_TOGGLE_EXPANDED_PIP.equals(action)) {
mDelegate.togglePipExpansion();
+ } else if (ACTION_FULLSCREEN.equals(action)) {
+ mDelegate.movePipToFullscreen();
}
}
}
interface Delegate {
void showPictureInPictureMenu();
+
void closePip();
+
void enterPipMovementMenu();
+
void togglePipExpansion();
+
+ void movePipToFullscreen();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index 64017e176fc3..d04c34916256 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -40,6 +40,8 @@ public enum ShellProtoLogGroup implements IProtoLogGroup {
Consts.TAG_WM_SHELL),
WM_SHELL_PICTURE_IN_PICTURE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG,
false, Consts.TAG_WM_SHELL),
+ WM_SHELL_SPLIT_SCREEN(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+ Consts.TAG_WM_SHELL),
TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest");
private final boolean mEnabled;
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 91f9d2522397..d543aa742377 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
@@ -54,6 +54,9 @@ import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_P
import static com.android.wm.shell.transition.Transitions.isClosingType;
import static com.android.wm.shell.transition.Transitions.isOpeningType;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -68,6 +71,7 @@ import android.content.res.Configuration;
import android.graphics.Rect;
import android.hardware.devicestate.DeviceStateManager;
import android.os.Bundle;
+import android.os.Debug;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
@@ -147,7 +151,9 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private final int mDisplayId;
private SplitLayout mSplitLayout;
+ private ValueAnimator mDividerFadeInAnimator;
private boolean mDividerVisible;
+ private boolean mKeyguardShowing;
private final SyncTransactionQueue mSyncQueue;
private final ShellTaskOrganizer mTaskOrganizer;
private final Context mContext;
@@ -404,6 +410,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitLayout.init();
// Set false to avoid record new bounds with old task still on top;
mShouldUpdateRecents = false;
+ mIsDividerRemoteAnimating = true;
final WindowContainerTransaction wct = new WindowContainerTransaction();
final WindowContainerTransaction evictWct = new WindowContainerTransaction();
prepareEvictChildTasks(SPLIT_POSITION_TOP_OR_LEFT, evictWct);
@@ -417,7 +424,6 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
RemoteAnimationTarget[] wallpapers,
RemoteAnimationTarget[] nonApps,
final IRemoteAnimationFinishedCallback finishedCallback) {
- mIsDividerRemoteAnimating = true;
RemoteAnimationTarget[] augmentedNonApps =
new RemoteAnimationTarget[nonApps.length + 1];
for (int i = 0; i < nonApps.length; ++i) {
@@ -494,8 +500,10 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
// Using legacy transitions, so we can't use blast sync since it conflicts.
mTaskOrganizer.applyTransaction(wct);
- mSyncQueue.runInSync(t ->
- updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */));
+ mSyncQueue.runInSync(t -> {
+ setDividerVisibility(true, t);
+ updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
+ });
}
private void onRemoteAnimationFinishedOrCancelled(WindowContainerTransaction evictWct) {
@@ -510,10 +518,6 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
} else {
mSyncQueue.queue(evictWct);
- mSyncQueue.runInSync(t -> {
- setDividerVisibility(true, t);
- updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
- });
}
}
@@ -623,16 +627,12 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
void onKeyguardVisibilityChanged(boolean showing) {
+ mKeyguardShowing = showing;
if (!mMainStage.isActive()) {
return;
}
- if (ENABLE_SHELL_TRANSITIONS) {
- // Update divider visibility so it won't float on top of keyguard.
- setDividerVisibility(!showing, null /* transaction */);
- }
-
- if (!showing && mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) {
+ if (!mKeyguardShowing && mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) {
if (ENABLE_SHELL_TRANSITIONS) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
prepareExitSplitScreen(mTopStageAfterFoldDismiss, wct);
@@ -643,7 +643,10 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage,
EXIT_REASON_DEVICE_FOLDED);
}
+ return;
}
+
+ setDividerVisibility(!mKeyguardShowing, null);
}
void onFinishedWakingUp() {
@@ -727,6 +730,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
setResizingSplits(false /* resizing */);
t.setWindowCrop(mMainStage.mRootLeash, null)
.setWindowCrop(mSideStage.mRootLeash, null);
+ setDividerVisibility(false, t);
});
// Hide divider and reset its position.
@@ -1055,8 +1059,31 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
private void setDividerVisibility(boolean visible, @Nullable SurfaceControl.Transaction t) {
+ if (visible == mDividerVisible) {
+ return;
+ }
+
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "%s: Request to %s divider bar from %s.", TAG,
+ (visible ? "show" : "hide"), Debug.getCaller());
+
+ // Defer showing divider bar after keyguard dismissed, so it won't interfere with keyguard
+ // dismissing animation.
+ if (visible && mKeyguardShowing) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "%s: Defer showing divider bar due to keyguard showing.", TAG);
+ return;
+ }
+
mDividerVisible = visible;
sendSplitVisibilityChanged();
+
+ if (mIsDividerRemoteAnimating) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "%s: Skip animating divider bar due to it's remote animating.", TAG);
+ return;
+ }
+
if (t != null) {
applyDividerVisibility(t);
} else {
@@ -1066,15 +1093,56 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private void applyDividerVisibility(SurfaceControl.Transaction t) {
final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
- if (mIsDividerRemoteAnimating || dividerLeash == null) return;
+ if (dividerLeash == null) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "%s: Skip animating divider bar due to divider leash not ready.", TAG);
+ return;
+ }
+ if (mIsDividerRemoteAnimating) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "%s: Skip animating divider bar due to it's remote animating.", TAG);
+ return;
+ }
+
+ if (mDividerFadeInAnimator != null && mDividerFadeInAnimator.isRunning()) {
+ mDividerFadeInAnimator.cancel();
+ }
if (mDividerVisible) {
- t.show(dividerLeash);
- t.setAlpha(dividerLeash, 1);
- t.setLayer(dividerLeash, Integer.MAX_VALUE);
- t.setPosition(dividerLeash,
- mSplitLayout.getRefDividerBounds().left,
- mSplitLayout.getRefDividerBounds().top);
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ mDividerFadeInAnimator = ValueAnimator.ofFloat(0f, 1f);
+ mDividerFadeInAnimator.addUpdateListener(animation -> {
+ if (dividerLeash == null) {
+ mDividerFadeInAnimator.cancel();
+ return;
+ }
+ transaction.setAlpha(dividerLeash, (float) animation.getAnimatedValue());
+ transaction.apply();
+ });
+ mDividerFadeInAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ if (dividerLeash == null) {
+ mDividerFadeInAnimator.cancel();
+ return;
+ }
+ transaction.show(dividerLeash);
+ transaction.setAlpha(dividerLeash, 0);
+ transaction.setLayer(dividerLeash, Integer.MAX_VALUE);
+ transaction.setPosition(dividerLeash,
+ mSplitLayout.getRefDividerBounds().left,
+ mSplitLayout.getRefDividerBounds().top);
+ transaction.apply();
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mTransactionPool.release(transaction);
+ mDividerFadeInAnimator = null;
+ }
+ });
+
+ mDividerFadeInAnimator.start();
} else {
t.hide(dividerLeash);
}
@@ -1096,10 +1164,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitLayout.init();
prepareEnterSplitScreen(wct);
mSyncQueue.queue(wct);
- mSyncQueue.runInSync(t -> {
- updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
- setDividerVisibility(true, t);
- });
+ mSyncQueue.runInSync(t ->
+ updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */));
}
if (mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) {
mShouldUpdateRecents = true;
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
index cf4ea467a29b..41cd31aabf05 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
@@ -37,7 +37,7 @@ class AppPairsHelper(
val displayBounds = WindowUtils.displayBounds
val secondaryAppBounds = Region.from(0,
dividerBounds.bounds.bottom - WindowUtils.dockedStackDividerInset,
- displayBounds.right, displayBounds.bottom - WindowUtils.navigationBarHeight)
+ displayBounds.right, displayBounds.bottom - WindowUtils.navigationBarFrameHeight)
return secondaryAppBounds
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
index a510d699387e..e2da1a4565c0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
@@ -171,7 +171,7 @@ class ResizeLegacySplitScreen(
val bottomAppBounds = Region.from(0,
dividerBounds.bottom - WindowUtils.dockedStackDividerInset,
displayBounds.right,
- displayBounds.bottom - WindowUtils.navigationBarHeight)
+ displayBounds.bottom - WindowUtils.navigationBarFrameHeight)
visibleRegion(Components.SimpleActivity.COMPONENT.toFlickerComponent())
.coversExactly(topAppBounds)
visibleRegion(Components.ImeActivity.COMPONENT.toFlickerComponent())
@@ -192,7 +192,7 @@ class ResizeLegacySplitScreen(
val bottomAppBounds = Region.from(0,
dividerBounds.bottom - WindowUtils.dockedStackDividerInset,
displayBounds.right,
- displayBounds.bottom - WindowUtils.navigationBarHeight)
+ displayBounds.bottom - WindowUtils.navigationBarFrameHeight)
visibleRegion(Components.SimpleActivity.COMPONENT.toFlickerComponent())
.coversExactly(topAppBounds)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 6cf88297f3b2..42b101439664 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -209,12 +209,11 @@ public class BackAnimationControllerTest {
createNavigationInfo(animationTarget, null, null,
BackNavigationInfo.TYPE_RETURN_TO_HOME, null);
- // Check that back start is dispatched.
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
- verify(mIOnBackInvokedCallback).onBackStarted();
- // Check that back progress is dispatched.
+ // Check that back start and progress is dispatched when first move.
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
+ verify(mIOnBackInvokedCallback).onBackStarted();
ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class);
verify(mIOnBackInvokedCallback).onBackProgressed(backEventCaptor.capture());
assertEquals(animationTarget, backEventCaptor.getValue().getDepartingAnimationTarget());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index 169f03e7bc3e..bde94d9d6c29 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -115,7 +115,7 @@ public class BubbleDataTest extends ShellTestCase {
private ArgumentCaptor<BubbleData.Update> mUpdateCaptor;
@Mock
- private Bubbles.SuppressionChangedListener mSuppressionListener;
+ private Bubbles.BubbleMetadataFlagListener mBubbleMetadataFlagListener;
@Mock
private Bubbles.PendingIntentCanceledListener mPendingIntentCanceledListener;
@@ -136,30 +136,47 @@ public class BubbleDataTest extends ShellTestCase {
mock(NotificationListenerService.Ranking.class);
when(ranking.isTextChanged()).thenReturn(true);
mEntryInterruptive = createBubbleEntry(1, "interruptive", "package.d", ranking);
- mBubbleInterruptive = new Bubble(mEntryInterruptive, mSuppressionListener, null,
+ mBubbleInterruptive = new Bubble(mEntryInterruptive, mBubbleMetadataFlagListener, null,
mMainExecutor);
mEntryDismissed = createBubbleEntry(1, "dismissed", "package.d", null);
- mBubbleDismissed = new Bubble(mEntryDismissed, mSuppressionListener, null,
+ mBubbleDismissed = new Bubble(mEntryDismissed, mBubbleMetadataFlagListener, null,
mMainExecutor);
mEntryLocusId = createBubbleEntry(1, "keyLocus", "package.e", null,
new LocusId("locusId1"));
- mBubbleLocusId = new Bubble(mEntryLocusId, mSuppressionListener, null, mMainExecutor);
+ mBubbleLocusId = new Bubble(mEntryLocusId,
+ mBubbleMetadataFlagListener,
+ null /* pendingIntentCanceledListener */,
+ mMainExecutor);
- mBubbleA1 = new Bubble(mEntryA1, mSuppressionListener, mPendingIntentCanceledListener,
+ mBubbleA1 = new Bubble(mEntryA1,
+ mBubbleMetadataFlagListener,
+ mPendingIntentCanceledListener,
mMainExecutor);
- mBubbleA2 = new Bubble(mEntryA2, mSuppressionListener, mPendingIntentCanceledListener,
+ mBubbleA2 = new Bubble(mEntryA2,
+ mBubbleMetadataFlagListener,
+ mPendingIntentCanceledListener,
mMainExecutor);
- mBubbleA3 = new Bubble(mEntryA3, mSuppressionListener, mPendingIntentCanceledListener,
+ mBubbleA3 = new Bubble(mEntryA3,
+ mBubbleMetadataFlagListener,
+ mPendingIntentCanceledListener,
mMainExecutor);
- mBubbleB1 = new Bubble(mEntryB1, mSuppressionListener, mPendingIntentCanceledListener,
+ mBubbleB1 = new Bubble(mEntryB1,
+ mBubbleMetadataFlagListener,
+ mPendingIntentCanceledListener,
mMainExecutor);
- mBubbleB2 = new Bubble(mEntryB2, mSuppressionListener, mPendingIntentCanceledListener,
+ mBubbleB2 = new Bubble(mEntryB2,
+ mBubbleMetadataFlagListener,
+ mPendingIntentCanceledListener,
mMainExecutor);
- mBubbleB3 = new Bubble(mEntryB3, mSuppressionListener, mPendingIntentCanceledListener,
+ mBubbleB3 = new Bubble(mEntryB3,
+ mBubbleMetadataFlagListener,
+ mPendingIntentCanceledListener,
mMainExecutor);
- mBubbleC1 = new Bubble(mEntryC1, mSuppressionListener, mPendingIntentCanceledListener,
+ mBubbleC1 = new Bubble(mEntryC1,
+ mBubbleMetadataFlagListener,
+ mPendingIntentCanceledListener,
mMainExecutor);
mPositioner = new TestableBubblePositioner(mContext,
mock(WindowManager.class));
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java
index 819a984b4a77..e8f3f69ca64e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java
@@ -63,7 +63,7 @@ public class BubbleTest extends ShellTestCase {
private Bubble mBubble;
@Mock
- private Bubbles.SuppressionChangedListener mSuppressionListener;
+ private Bubbles.BubbleMetadataFlagListener mBubbleMetadataFlagListener;
@Before
public void setUp() {
@@ -81,7 +81,7 @@ public class BubbleTest extends ShellTestCase {
when(mNotif.getBubbleMetadata()).thenReturn(metadata);
when(mSbn.getKey()).thenReturn("mock");
mBubbleEntry = new BubbleEntry(mSbn, null, true, false, false, false);
- mBubble = new Bubble(mBubbleEntry, mSuppressionListener, null, mMainExecutor);
+ mBubble = new Bubble(mBubbleEntry, mBubbleMetadataFlagListener, null, mMainExecutor);
}
@Test
@@ -144,22 +144,22 @@ public class BubbleTest extends ShellTestCase {
}
@Test
- public void testSuppressionListener_change_notified() {
+ public void testBubbleMetadataFlagListener_change_notified() {
assertThat(mBubble.showInShade()).isTrue();
mBubble.setSuppressNotification(true);
assertThat(mBubble.showInShade()).isFalse();
- verify(mSuppressionListener).onBubbleNotificationSuppressionChange(mBubble);
+ verify(mBubbleMetadataFlagListener).onBubbleMetadataFlagChanged(mBubble);
}
@Test
- public void testSuppressionListener_noChange_doesntNotify() {
+ public void testBubbleMetadataFlagListener_noChange_doesntNotify() {
assertThat(mBubble.showInShade()).isTrue();
mBubble.setSuppressNotification(false);
- verify(mSuppressionListener, never()).onBubbleNotificationSuppressionChange(any());
+ verify(mBubbleMetadataFlagListener, never()).onBubbleMetadataFlagChanged(any());
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 5368b7db3dc1..df18133adcfb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -45,6 +45,7 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.onehanded.OneHandedController;
+import com.android.wm.shell.pip.PipAppOpsListener;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMediaController;
@@ -75,7 +76,6 @@ public class PipControllerTest extends ShellTestCase {
@Mock private PhonePipMenuController mMockPhonePipMenuController;
@Mock private PipAppOpsListener mMockPipAppOpsListener;
@Mock private PipBoundsAlgorithm mMockPipBoundsAlgorithm;
- @Mock private PipKeepClearAlgorithm mMockPipKeepClearAlgorithm;
@Mock private PipSnapAlgorithm mMockPipSnapAlgorithm;
@Mock private PipMediaController mMockPipMediaController;
@Mock private PipTaskOrganizer mMockPipTaskOrganizer;
@@ -100,12 +100,12 @@ public class PipControllerTest extends ShellTestCase {
return null;
}).when(mMockExecutor).execute(any());
mPipController = new PipController(mContext, mMockDisplayController,
- mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
- mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
+ mMockPipAppOpsListener, mMockPipBoundsAlgorithm,
+ mMockPipBoundsState, mMockPipMediaController,
mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTouchHandler,
mMockPipTransitionController, mMockWindowManagerShellWrapper,
- mMockTaskStackListener, mPipParamsChangedForwarder, mMockOneHandedController,
- mMockExecutor);
+ mMockTaskStackListener, mPipParamsChangedForwarder,
+ mMockOneHandedController, mMockExecutor);
when(mMockPipBoundsAlgorithm.getSnapAlgorithm()).thenReturn(mMockPipSnapAlgorithm);
when(mMockPipTouchHandler.getMotionHelper()).thenReturn(mMockPipMotionHelper);
}
@@ -133,12 +133,12 @@ public class PipControllerTest extends ShellTestCase {
when(spyContext.getPackageManager()).thenReturn(mockPackageManager);
assertNull(PipController.create(spyContext, mMockDisplayController,
- mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
- mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
+ mMockPipAppOpsListener, mMockPipBoundsAlgorithm,
+ mMockPipBoundsState, mMockPipMediaController,
mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTouchHandler,
mMockPipTransitionController, mMockWindowManagerShellWrapper,
- mMockTaskStackListener, mPipParamsChangedForwarder, mMockOneHandedController,
- mMockExecutor));
+ mMockTaskStackListener, mPipParamsChangedForwarder,
+ mMockOneHandedController, mMockExecutor));
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithmTest.java
deleted file mode 100644
index f657b5e62d82..000000000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithmTest.java
+++ /dev/null
@@ -1,96 +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.wm.shell.pip.phone;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-
-import android.graphics.Rect;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.wm.shell.ShellTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Set;
-
-/**
- * Unit tests against {@link PipKeepClearAlgorithm}.
- */
-@RunWith(AndroidTestingRunner.class)
-@SmallTest
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class PipKeepClearAlgorithmTest extends ShellTestCase {
-
- private PipKeepClearAlgorithm mPipKeepClearAlgorithm;
-
-
- @Before
- public void setUp() throws Exception {
- mPipKeepClearAlgorithm = new PipKeepClearAlgorithm();
- }
-
- @Test
- public void adjust_withCollidingRestrictedKeepClearAreas_movesBounds() {
- final Rect inBounds = new Rect(0, 0, 100, 100);
- final Rect keepClearRect = new Rect(50, 50, 150, 150);
-
- final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(keepClearRect),
- Set.of());
-
- assertFalse(outBounds.contains(keepClearRect));
- }
-
- @Test
- public void adjust_withNonCollidingRestrictedKeepClearAreas_boundsDoNotChange() {
- final Rect inBounds = new Rect(0, 0, 100, 100);
- final Rect keepClearRect = new Rect(100, 100, 150, 150);
-
- final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(keepClearRect),
- Set.of());
-
- assertEquals(inBounds, outBounds);
- }
-
- @Test
- public void adjust_withCollidingUnrestrictedKeepClearAreas_boundsDoNotChange() {
- // TODO(b/183746978): update this test to accommodate for the updated algorithm
- final Rect inBounds = new Rect(0, 0, 100, 100);
- final Rect keepClearRect = new Rect(50, 50, 150, 150);
-
- final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(),
- Set.of(keepClearRect));
-
- assertEquals(inBounds, outBounds);
- }
-
- @Test
- public void adjust_withNonCollidingUnrestrictedKeepClearAreas_boundsDoNotChange() {
- final Rect inBounds = new Rect(0, 0, 100, 100);
- final Rect keepClearRect = new Rect(100, 100, 150, 150);
-
- final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(),
- Set.of(keepClearRect));
-
- assertEquals(inBounds, outBounds);
- }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index a55f737f2f25..ffaab652aa99 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -139,6 +139,7 @@ public class SplitTransitionTests extends ShellTestCase {
}
@Test
+ @UiThreadTest
public void testLaunchToSide() {
ActivityManager.RunningTaskInfo newTask = new TestRunningTaskInfoBuilder()
.setParentTaskId(mSideStage.mRootTaskInfo.taskId).build();
@@ -173,6 +174,7 @@ public class SplitTransitionTests extends ShellTestCase {
}
@Test
+ @UiThreadTest
public void testLaunchPair() {
TransitionInfo info = createEnterPairInfo();
@@ -195,6 +197,7 @@ public class SplitTransitionTests extends ShellTestCase {
}
@Test
+ @UiThreadTest
public void testMonitorInSplit() {
enterSplit();
@@ -251,6 +254,7 @@ public class SplitTransitionTests extends ShellTestCase {
}
@Test
+ @UiThreadTest
public void testEnterRecents() {
enterSplit();
@@ -288,6 +292,7 @@ public class SplitTransitionTests extends ShellTestCase {
}
@Test
+ @UiThreadTest
public void testDismissFromBeingOccluded() {
enterSplit();
@@ -325,6 +330,7 @@ public class SplitTransitionTests extends ShellTestCase {
}
@Test
+ @UiThreadTest
public void testDismissFromMultiWindowSupport() {
enterSplit();
@@ -346,6 +352,7 @@ public class SplitTransitionTests extends ShellTestCase {
}
@Test
+ @UiThreadTest
public void testDismissSnap() {
enterSplit();
@@ -370,6 +377,7 @@ public class SplitTransitionTests extends ShellTestCase {
}
@Test
+ @UiThreadTest
public void testDismissFromAppFinish() {
enterSplit();
diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp
index e7432ac5f216..90c4440c8339 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.cpp
+++ b/libs/hwui/pipeline/skia/ShaderCache.cpp
@@ -136,24 +136,59 @@ sk_sp<SkData> ShaderCache::load(const SkData& key) {
free(valueBuffer);
return nullptr;
}
+ mNumShadersCachedInRam++;
+ ATRACE_FORMAT("HWUI RAM cache: %d shaders", mNumShadersCachedInRam);
return SkData::MakeFromMalloc(valueBuffer, valueSize);
}
+namespace {
+// Helper for BlobCache::set to trace the result.
+void set(BlobCache* cache, const void* key, size_t keySize, const void* value, size_t valueSize) {
+ switch (cache->set(key, keySize, value, valueSize)) {
+ case BlobCache::InsertResult::kInserted:
+ // This is what we expect/hope. It means the cache is large enough.
+ return;
+ case BlobCache::InsertResult::kDidClean: {
+ ATRACE_FORMAT("ShaderCache: evicted an entry to fit {key: %lu value %lu}!", keySize,
+ valueSize);
+ return;
+ }
+ case BlobCache::InsertResult::kNotEnoughSpace: {
+ ATRACE_FORMAT("ShaderCache: could not fit {key: %lu value %lu}!", keySize, valueSize);
+ return;
+ }
+ case BlobCache::InsertResult::kInvalidValueSize:
+ case BlobCache::InsertResult::kInvalidKeySize: {
+ ATRACE_FORMAT("ShaderCache: invalid size {key: %lu value %lu}!", keySize, valueSize);
+ return;
+ }
+ case BlobCache::InsertResult::kKeyTooBig:
+ case BlobCache::InsertResult::kValueTooBig:
+ case BlobCache::InsertResult::kCombinedTooBig: {
+ ATRACE_FORMAT("ShaderCache: entry too big: {key: %lu value %lu}!", keySize, valueSize);
+ return;
+ }
+ }
+}
+} // namespace
+
void ShaderCache::saveToDiskLocked() {
ATRACE_NAME("ShaderCache::saveToDiskLocked");
if (mInitialized && mBlobCache && mSavePending) {
if (mIDHash.size()) {
auto key = sIDKey;
- mBlobCache->set(&key, sizeof(key), mIDHash.data(), mIDHash.size());
+ set(mBlobCache.get(), &key, sizeof(key), mIDHash.data(), mIDHash.size());
}
mBlobCache->writeToFile();
}
mSavePending = false;
}
-void ShaderCache::store(const SkData& key, const SkData& data) {
+void ShaderCache::store(const SkData& key, const SkData& data, const SkString& /*description*/) {
ATRACE_NAME("ShaderCache::store");
std::lock_guard<std::mutex> lock(mMutex);
+ mNumShadersCachedInRam++;
+ ATRACE_FORMAT("HWUI RAM cache: %d shaders", mNumShadersCachedInRam);
if (!mInitialized) {
return;
@@ -187,7 +222,7 @@ void ShaderCache::store(const SkData& key, const SkData& data) {
mNewPipelineCacheSize = -1;
mTryToStorePipelineCache = true;
}
- bc->set(key.data(), keySize, value, valueSize);
+ set(bc, key.data(), keySize, value, valueSize);
if (!mSavePending && mDeferredSaveDelay > 0) {
mSavePending = true;
diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h
index 4dcc9fb49802..3e0fd5164011 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.h
+++ b/libs/hwui/pipeline/skia/ShaderCache.h
@@ -73,7 +73,7 @@ public:
* "store" attempts to insert a new key/value blob pair into the cache.
* This will be called by Skia after it compiled a new SKSL shader
*/
- void store(const SkData& key, const SkData& data) override;
+ void store(const SkData& key, const SkData& data, const SkString& description) override;
/**
* "onVkFrameFlushed" tries to store Vulkan pipeline cache state.
@@ -210,6 +210,13 @@ private:
*/
static constexpr uint8_t sIDKey = 0;
+ /**
+ * Most of this class concerns persistent storage for shaders, but it's also
+ * interesting to keep track of how many shaders are stored in RAM. This
+ * class provides a convenient entry point for that.
+ */
+ int mNumShadersCachedInRam = 0;
+
friend class ShaderCacheTestUtils; // used for unit testing
};
diff --git a/libs/hwui/tests/unit/ShaderCacheTests.cpp b/libs/hwui/tests/unit/ShaderCacheTests.cpp
index 87981f115763..974d85a453db 100644
--- a/libs/hwui/tests/unit/ShaderCacheTests.cpp
+++ b/libs/hwui/tests/unit/ShaderCacheTests.cpp
@@ -140,9 +140,9 @@ TEST(ShaderCacheTest, testWriteAndRead) {
// write to the in-memory cache without storing on disk and verify we read the same values
sk_sp<SkData> inVS;
setShader(inVS, "sassas");
- ShaderCache::get().store(GrProgramDescTest(100), *inVS.get());
+ ShaderCache::get().store(GrProgramDescTest(100), *inVS.get(), SkString());
setShader(inVS, "someVS");
- ShaderCache::get().store(GrProgramDescTest(432), *inVS.get());
+ ShaderCache::get().store(GrProgramDescTest(432), *inVS.get(), SkString());
ASSERT_NE((outVS = ShaderCache::get().load(GrProgramDescTest(100))), sk_sp<SkData>());
ASSERT_TRUE(checkShader(outVS, "sassas"));
ASSERT_NE((outVS = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>());
@@ -166,7 +166,7 @@ TEST(ShaderCacheTest, testWriteAndRead) {
// change data, store to disk, read back again and verify data has been changed
setShader(inVS, "ewData1");
- ShaderCache::get().store(GrProgramDescTest(432), *inVS.get());
+ ShaderCache::get().store(GrProgramDescTest(432), *inVS.get(), SkString());
ShaderCacheTestUtils::terminate(ShaderCache::get(), true);
ShaderCache::get().initShaderDiskCache();
ASSERT_NE((outVS2 = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>());
@@ -177,7 +177,7 @@ TEST(ShaderCacheTest, testWriteAndRead) {
std::vector<uint8_t> dataBuffer(dataSize);
genRandomData(dataBuffer);
setShader(inVS, dataBuffer);
- ShaderCache::get().store(GrProgramDescTest(432), *inVS.get());
+ ShaderCache::get().store(GrProgramDescTest(432), *inVS.get(), SkString());
ShaderCacheTestUtils::terminate(ShaderCache::get(), true);
ShaderCache::get().initShaderDiskCache();
ASSERT_NE((outVS2 = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>());
@@ -225,7 +225,7 @@ TEST(ShaderCacheTest, testCacheValidation) {
setShader(data, dataBuffer);
blob = std::make_pair(key, data);
- ShaderCache::get().store(*key.get(), *data.get());
+ ShaderCache::get().store(*key.get(), *data.get(), SkString());
}
ShaderCacheTestUtils::terminate(ShaderCache::get(), true);
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 1dc74e5f7740..10ea6512c724 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -106,6 +106,7 @@ PointerController::PointerController(const sp<PointerControllerPolicyInterface>&
PointerController::~PointerController() {
mDisplayInfoListener->onPointerControllerDestroyed();
mUnregisterWindowInfosListener(mDisplayInfoListener);
+ mContext.getPolicy()->onPointerDisplayIdChanged(ADISPLAY_ID_NONE, 0, 0);
}
std::mutex& PointerController::getLock() const {
@@ -255,6 +256,12 @@ void PointerController::setDisplayViewport(const DisplayViewport& viewport) {
getAdditionalMouseResources = true;
}
mCursorController.setDisplayViewport(viewport, getAdditionalMouseResources);
+ if (viewport.displayId != mLocked.pointerDisplayId) {
+ float xPos, yPos;
+ mCursorController.getPosition(&xPos, &yPos);
+ mContext.getPolicy()->onPointerDisplayIdChanged(viewport.displayId, xPos, yPos);
+ mLocked.pointerDisplayId = viewport.displayId;
+ }
}
void PointerController::updatePointerIcon(int32_t iconId) {
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 2e6e851ee15a..eab030f71e1a 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -104,6 +104,7 @@ private:
struct Locked {
Presentation presentation;
+ int32_t pointerDisplayId = ADISPLAY_ID_NONE;
std::vector<gui::DisplayInfo> mDisplayInfos;
std::unordered_map<int32_t /* displayId */, TouchSpotController> spotControllers;
diff --git a/libs/input/PointerControllerContext.h b/libs/input/PointerControllerContext.h
index 26a65a47471d..c2bc1e020279 100644
--- a/libs/input/PointerControllerContext.h
+++ b/libs/input/PointerControllerContext.h
@@ -79,6 +79,7 @@ public:
std::map<int32_t, PointerAnimation>* outAnimationResources, int32_t displayId) = 0;
virtual int32_t getDefaultPointerIconId() = 0;
virtual int32_t getCustomPointerIconId() = 0;
+ virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos) = 0;
};
/*
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index dae1fccec804..f9752ed155df 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -56,9 +56,11 @@ public:
std::map<int32_t, PointerAnimation>* outAnimationResources, int32_t displayId) override;
virtual int32_t getDefaultPointerIconId() override;
virtual int32_t getCustomPointerIconId() override;
+ virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos) override;
bool allResourcesAreLoaded();
bool noResourcesAreLoaded();
+ std::optional<int32_t> getLastReportedPointerDisplayId() { return latestPointerDisplayId; }
private:
void loadPointerIconForType(SpriteIcon* icon, int32_t cursorType);
@@ -66,6 +68,7 @@ private:
bool pointerIconLoaded{false};
bool pointerResourcesLoaded{false};
bool additionalMouseResourcesLoaded{false};
+ std::optional<int32_t /*displayId*/> latestPointerDisplayId;
};
void MockPointerControllerPolicyInterface::loadPointerIcon(SpriteIcon* icon, int32_t) {
@@ -126,12 +129,19 @@ void MockPointerControllerPolicyInterface::loadPointerIconForType(SpriteIcon* ic
icon->hotSpotX = hotSpot.first;
icon->hotSpotY = hotSpot.second;
}
+
+void MockPointerControllerPolicyInterface::onPointerDisplayIdChanged(int32_t displayId,
+ float /*xPos*/,
+ float /*yPos*/) {
+ latestPointerDisplayId = displayId;
+}
+
class PointerControllerTest : public Test {
protected:
PointerControllerTest();
~PointerControllerTest();
- void ensureDisplayViewportIsSet();
+ void ensureDisplayViewportIsSet(int32_t displayId = ADISPLAY_ID_DEFAULT);
sp<MockSprite> mPointerSprite;
sp<MockPointerControllerPolicyInterface> mPolicy;
@@ -168,9 +178,9 @@ PointerControllerTest::~PointerControllerTest() {
mThread.join();
}
-void PointerControllerTest::ensureDisplayViewportIsSet() {
+void PointerControllerTest::ensureDisplayViewportIsSet(int32_t displayId) {
DisplayViewport viewport;
- viewport.displayId = ADISPLAY_ID_DEFAULT;
+ viewport.displayId = displayId;
viewport.logicalRight = 1600;
viewport.logicalBottom = 1200;
viewport.physicalRight = 800;
@@ -255,6 +265,30 @@ TEST_F(PointerControllerTest, doesNotGetResourcesBeforeSettingViewport) {
ensureDisplayViewportIsSet();
}
+TEST_F(PointerControllerTest, notifiesPolicyWhenPointerDisplayChanges) {
+ EXPECT_FALSE(mPolicy->getLastReportedPointerDisplayId())
+ << "A pointer display change does not occur when PointerController is created.";
+
+ ensureDisplayViewportIsSet(ADISPLAY_ID_DEFAULT);
+
+ const auto lastReportedPointerDisplayId = mPolicy->getLastReportedPointerDisplayId();
+ ASSERT_TRUE(lastReportedPointerDisplayId)
+ << "The policy is notified of a pointer display change when the viewport is first set.";
+ EXPECT_EQ(ADISPLAY_ID_DEFAULT, *lastReportedPointerDisplayId)
+ << "Incorrect pointer display notified.";
+
+ ensureDisplayViewportIsSet(42);
+
+ EXPECT_EQ(42, *mPolicy->getLastReportedPointerDisplayId())
+ << "The policy is notified when the pointer display changes.";
+
+ // Release the PointerController.
+ mPointerController = nullptr;
+
+ EXPECT_EQ(ADISPLAY_ID_NONE, *mPolicy->getLastReportedPointerDisplayId())
+ << "The pointer display changes to invalid when PointerController is destroyed.";
+}
+
class PointerControllerWindowInfoListenerTest : public Test {};
class TestPointerController : public PointerController {
diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java
index 70d6810d01ec..472586b5e519 100644
--- a/media/java/android/media/ImageReader.java
+++ b/media/java/android/media/ImageReader.java
@@ -643,6 +643,9 @@ public class ImageReader implements AutoCloseable {
/**
* <p>Return the frame to the ImageReader for reuse.</p>
+ *
+ * This method should only be called via {@link SurfaceImage#close} which ensures that image
+ * closing is atomic.
*/
private void releaseImage(Image i) {
if (! (i instanceof SurfaceImage) ) {
@@ -1125,6 +1128,8 @@ public class ImageReader implements AutoCloseable {
}
private class SurfaceImage extends android.media.Image {
+ private final Object mCloseLock = new Object();
+
public SurfaceImage(int format) {
mFormat = format;
mHardwareBufferFormat = ImageReader.this.mHardwareBufferFormat;
@@ -1139,7 +1144,9 @@ public class ImageReader implements AutoCloseable {
@Override
public void close() {
- ImageReader.this.releaseImage(this);
+ synchronized (this.mCloseLock) {
+ ImageReader.this.releaseImage(this);
+ }
}
public ImageReader getReader() {
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index 37cbf3017f0b..5e918641e49f 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -484,6 +484,14 @@ public class CompanionDeviceActivity extends FragmentActivity implements
if (deviceFilterPairs.isEmpty()) return;
mSelectedDevice = requireNonNull(deviceFilterPairs.get(0));
+ // No need to show user consent dialog if it is a singleDevice
+ // and isSkipPrompt(true) AssociationRequest.
+ // See AssociationRequestsProcessor#mayAssociateWithoutPrompt.
+ if (mRequest.isSkipPrompt()) {
+ mSingleDeviceSpinner.setVisibility(View.GONE);
+ onUserSelectedDevice(mSelectedDevice);
+ return;
+ }
final String deviceName = mSelectedDevice.getDisplayName();
final Spanned title = getHtmlFromResources(
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
index e8a1a5cc1916..f333b86d4d52 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
@@ -150,8 +150,6 @@ public class CompanionDeviceDiscoveryService extends Service {
mBtAdapter = mBtManager.getAdapter();
mBleScanner = mBtAdapter.getBluetoothLeScanner();
mWifiManager = getSystemService(WifiManager.class);
-
- sScanResultsLiveData.setValue(Collections.emptyList());
}
@Override
@@ -175,6 +173,7 @@ public class CompanionDeviceDiscoveryService extends Service {
@Override
public void onDestroy() {
+ sScanResultsLiveData.setValue(Collections.emptyList());
super.onDestroy();
if (DEBUG) Log.d(TAG, "onDestroy()");
}
@@ -188,6 +187,7 @@ public class CompanionDeviceDiscoveryService extends Service {
mStopAfterFirstMatch = request.isSingleDevice();
mDiscoveryStarted = true;
sStateLiveData.setValue(DiscoveryState.DISCOVERY_IN_PROGRESS);
+ sScanResultsLiveData.setValue(Collections.emptyList());
final List<DeviceFilter<?>> allFilters = request.getDeviceFilters();
final List<BluetoothDeviceFilter> btFilters =
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_content_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_content_layout.xml
index 25f0771b2170..72b569f22d6c 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_content_layout.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_content_layout.xml
@@ -25,7 +25,7 @@
android:fitsSystemWindows="true"
android:outlineAmbientShadowColor="@android:color/transparent"
android:outlineSpotShadowColor="@android:color/transparent"
- android:background="?android:attr/colorPrimary"
+ android:background="@android:color/transparent"
android:theme="@style/Theme.CollapsingToolbar.Settings">
<com.google.android.material.appbar.CollapsingToolbarLayout
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
index 72383fe59e7e..dbb4b5017e6b 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
@@ -20,6 +20,7 @@ import android.app.ActionBar;
import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
+import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
@@ -29,6 +30,7 @@ import android.widget.Toolbar;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
@@ -41,6 +43,7 @@ import com.google.android.material.appbar.CollapsingToolbarLayout;
* This widget is wrapping the collapsing toolbar and can be directly used by the
* {@link AppCompatActivity}.
*/
+@RequiresApi(Build.VERSION_CODES.S)
public class CollapsingCoordinatorLayout extends CoordinatorLayout {
private static final String TAG = "CollapsingCoordinatorLayout";
private static final float TOOLBAR_LINE_SPACING_MULTIPLIER = 1.1f;
diff --git a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
index ac306361386e..6766cdd0beb6 100644
--- a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
+++ b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
@@ -40,6 +40,8 @@ public class FooterPreference extends Preference {
static final int ORDER_FOOTER = Integer.MAX_VALUE - 1;
@VisibleForTesting
View.OnClickListener mLearnMoreListener;
+ @VisibleForTesting
+ int mIconVisibility = View.VISIBLE;
private CharSequence mContentDescription;
private CharSequence mLearnMoreText;
private CharSequence mLearnMoreContentDescription;
@@ -84,6 +86,9 @@ public class FooterPreference extends Preference {
} else {
learnMore.setVisibility(View.GONE);
}
+
+ View icon = holder.itemView.findViewById(R.id.icon_frame);
+ icon.setVisibility(mIconVisibility);
}
@Override
@@ -165,6 +170,17 @@ public class FooterPreference extends Preference {
}
}
+ /**
+ * Set visibility of footer icon.
+ */
+ public void setIconVisibility(int iconVisibility) {
+ if (mIconVisibility == iconVisibility) {
+ return;
+ }
+ mIconVisibility = iconVisibility;
+ notifyChanged();
+ }
+
private void init() {
setLayoutResource(R.layout.preference_footer);
if (getIcon() == null) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
index 284da73efb6f..2f30baa79b9d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
@@ -234,5 +234,10 @@ public class RestrictedPreferenceHelper {
if (!(mPreference instanceof RestrictedTopLevelPreference)) {
mPreference.setEnabled(!(mDisabledByAdmin || mDisabledByAppOps));
}
+
+ if (mPreference instanceof PrimarySwitchPreference) {
+ ((PrimarySwitchPreference) mPreference)
+ .setSwitchEnabled(!(mDisabledByAdmin || mDisabledByAppOps));
+ }
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
index bdeb4740d305..e6160bb9896d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
@@ -16,10 +16,14 @@
package com.android.settingslib;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.DISABLED_BY_ADMIN_SWITCH_SUMMARY;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.ENABLED_BY_ADMIN_SWITCH_SUMMARY;
+
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import android.annotation.NonNull;
import android.app.AppOpsManager;
+import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Process;
@@ -37,6 +41,8 @@ import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceViewHolder;
import androidx.preference.SwitchPreference;
+import com.android.settingslib.utils.BuildCompatUtils;
+
/**
* Version of SwitchPreference that can be disabled by a device admin
* using a user restriction.
@@ -117,8 +123,13 @@ public class RestrictedSwitchPreference extends SwitchPreference {
CharSequence switchSummary;
if (mRestrictedSwitchSummary == null) {
- switchSummary = getContext().getText(isChecked()
- ? R.string.enabled_by_admin : R.string.disabled_by_admin);
+ switchSummary = isChecked()
+ ? getUpdatableEnterpriseString(
+ getContext(), ENABLED_BY_ADMIN_SWITCH_SUMMARY,
+ R.string.enabled_by_admin)
+ : getUpdatableEnterpriseString(
+ getContext(), DISABLED_BY_ADMIN_SWITCH_SUMMARY,
+ R.string.disabled_by_admin);
} else {
switchSummary = mRestrictedSwitchSummary;
}
@@ -153,6 +164,16 @@ public class RestrictedSwitchPreference extends SwitchPreference {
}
}
+ private static String getUpdatableEnterpriseString(
+ Context context, String updatableStringId, int resId) {
+ if (!BuildCompatUtils.isAtLeastT()) {
+ return context.getString(resId);
+ }
+ return context.getSystemService(DevicePolicyManager.class).getResources().getString(
+ updatableStringId,
+ () -> context.getString(resId));
+ }
+
@Override
public void performClick() {
if (!mHelper.performClick()) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index fdb06072bbd1..6b9daa35f9ca 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -863,6 +863,30 @@ public class ApplicationsState {
}
}
+ /**
+ * Activate session to enable a class that implements Callbacks to receive the callback.
+ */
+ public void activateSession() {
+ synchronized (mEntriesMap) {
+ if (!mResumed) {
+ mResumed = true;
+ mSessionsChanged = true;
+ }
+ }
+ }
+
+ /**
+ * Deactivate session to disable a class that implements Callbacks to get the callback.
+ */
+ public void deactivateSession() {
+ synchronized (mEntriesMap) {
+ if (mResumed) {
+ mResumed = false;
+ mSessionsChanged = true;
+ }
+ }
+ }
+
public ArrayList<AppEntry> getAllApps() {
synchronized (mEntriesMap) {
return new ArrayList<>(mAppEntries);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 4ee21229e364..6919cf237853 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -1376,7 +1376,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
/**
* Store the member devices that are in the same coordinated set.
*/
- public void setMemberDevice(CachedBluetoothDevice memberDevice) {
+ public void addMemberDevice(CachedBluetoothDevice memberDevice) {
mMemberDevices.add(memberDevice);
}
@@ -1393,24 +1393,24 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
* device and member devices.
*
* @param prevMainDevice the previous Main device, it will be added into the member device set.
- * @param newMainDevie the new Main device, it will be removed from the member device set.
+ * @param newMainDevice the new Main device, it will be removed from the member device set.
*/
public void switchMemberDeviceContent(CachedBluetoothDevice prevMainDevice,
- CachedBluetoothDevice newMainDevie) {
+ CachedBluetoothDevice newMainDevice) {
// Backup from main device
final BluetoothDevice tmpDevice = mDevice;
final short tmpRssi = mRssi;
final boolean tmpJustDiscovered = mJustDiscovered;
// Set main device from sub device
- mDevice = newMainDevie.mDevice;
- mRssi = newMainDevie.mRssi;
- mJustDiscovered = newMainDevie.mJustDiscovered;
- setMemberDevice(prevMainDevice);
- mMemberDevices.remove(newMainDevie);
+ mDevice = newMainDevice.mDevice;
+ mRssi = newMainDevice.mRssi;
+ mJustDiscovered = newMainDevice.mJustDiscovered;
+ addMemberDevice(prevMainDevice);
+ mMemberDevices.remove(newMainDevice);
// Set sub device from backup
- newMainDevie.mDevice = tmpDevice;
- newMainDevie.mRssi = tmpRssi;
- newMainDevie.mJustDiscovered = tmpJustDiscovered;
+ newMainDevice.mDevice = tmpDevice;
+ newMainDevice.mRssi = tmpRssi;
+ newMainDevice.mJustDiscovered = tmpJustDiscovered;
fetchActiveDevices();
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index cc56a212aea1..89e10c4b5e11 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -85,7 +85,7 @@ public class CsipDeviceManager {
// Once there is other devices with the same groupId, to add new device as member
// devices.
if (CsipDevice != null) {
- CsipDevice.setMemberDevice(newDevice);
+ CsipDevice.addMemberDevice(newDevice);
newDevice.setName(CsipDevice.getName());
return true;
}
@@ -148,7 +148,7 @@ public class CsipDeviceManager {
log("onGroupIdChanged: removed from UI device =" + cachedDevice
+ ", with groupId=" + groupId + " firstMatchedIndex=" + firstMatchedIndex);
- mainDevice.setMemberDevice(cachedDevice);
+ mainDevice.addMemberDevice(cachedDevice);
mCachedDevices.remove(i);
mBtManager.getEventManager().dispatchDeviceRemoved(cachedDevice);
break;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
index c248fff09d6f..b0392be9d336 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
@@ -26,6 +26,7 @@ import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastAssistant;
import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothProfile.ServiceListener;
import android.content.Context;
@@ -202,6 +203,43 @@ public class LocalBluetoothLeBroadcastAssistant implements LocalBluetoothProfile
mService.startSearchingForSources(filters);
}
+ /**
+ * Return true if a search has been started by this application.
+ *
+ * @return true if a search has been started by this application
+ * @hide
+ */
+ public boolean isSearchInProgress() {
+ if (DEBUG) {
+ Log.d(TAG, "isSearchInProgress()");
+ }
+ if (mService == null) {
+ Log.d(TAG, "The BluetoothLeBroadcastAssistant is null");
+ return false;
+ }
+ return mService.isSearchInProgress();
+ }
+
+ /**
+ * Get information about all Broadcast Sources that a Broadcast Sink knows about.
+ *
+ * @param sink Broadcast Sink from which to get all Broadcast Sources
+ * @return the list of Broadcast Receive State {@link BluetoothLeBroadcastReceiveState}
+ * stored in the Broadcast Sink
+ * @throws NullPointerException when <var>sink</var> is null
+ */
+ public @NonNull List<BluetoothLeBroadcastReceiveState> getAllSources(
+ @NonNull BluetoothDevice sink) {
+ if (DEBUG) {
+ Log.d(TAG, "getAllSources()");
+ }
+ if (mService == null) {
+ Log.d(TAG, "The BluetoothLeBroadcastAssistant is null");
+ return new ArrayList<BluetoothLeBroadcastReceiveState>();
+ }
+ return mService.getAllSources(sink);
+ }
+
public void registerServiceCallBack(@NonNull @CallbackExecutor Executor executor,
@NonNull BluetoothLeBroadcastAssistant.Callback callback) {
if (mService == null) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java
index 5b1fefb249a3..aff9a6e3c08d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java
@@ -25,6 +25,7 @@ import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastSubgroup;
import android.util.Log;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -95,6 +96,7 @@ public class LocalBluetoothLeBroadcastMetadata {
}
public String convertToQrCodeString() {
+ String subgroupString = convertSubgroupToString(mSubgroupList);
return new StringBuilder()
.append(BluetoothBroadcastUtils.SCHEME_BT_BROADCAST_METADATA)
.append(BluetoothBroadcastUtils.PREFIX_BT_ADDRESS_TYPE)
@@ -122,11 +124,83 @@ public class LocalBluetoothLeBroadcastMetadata {
.append(METADATA_START).append(mPresentationDelayMicros).append(METADATA_END)
.append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
.append(BluetoothBroadcastUtils.PREFIX_BT_SUBGROUPS)
- .append(METADATA_START).append(mSubgroupList).append(METADATA_END)
+ .append(METADATA_START).append(subgroupString).append(METADATA_END)
.append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
.toString();
}
+ private String convertSubgroupToString(List<BluetoothLeBroadcastSubgroup> subgroupList) {
+ StringBuilder subgroupListBuilder = new StringBuilder();
+ String subgroupString = "";
+ for (BluetoothLeBroadcastSubgroup subgroup: subgroupList) {
+ String audioCodec = convertAudioCodecConfigToString(subgroup.getCodecSpecificConfig());
+ String audioContent = convertAudioContentToString(subgroup.getContentMetadata());
+ String channels = convertChannelToString(subgroup.getChannels());
+ subgroupString = new StringBuilder()
+ .append(BluetoothBroadcastUtils.PREFIX_BTSG_CODEC_ID)
+ .append(METADATA_START).append(subgroup.getCodecId()).append(METADATA_END)
+ .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+ .append(BluetoothBroadcastUtils.PREFIX_BTSG_CODEC_CONFIG)
+ .append(METADATA_START).append(audioCodec).append(METADATA_END)
+ .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+ .append(BluetoothBroadcastUtils.PREFIX_BTSG_AUDIO_CONTENT)
+ .append(METADATA_START).append(audioContent).append(METADATA_END)
+ .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+ .append(BluetoothBroadcastUtils.PREFIX_BTSG_BROADCAST_CHANNEL)
+ .append(METADATA_START).append(channels).append(METADATA_END)
+ .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+ .toString();
+ subgroupListBuilder.append(subgroupString);
+ }
+ return subgroupListBuilder.toString();
+ }
+
+ private String convertAudioCodecConfigToString(BluetoothLeAudioCodecConfigMetadata config) {
+ String audioLocation = String.valueOf(config.getAudioLocation());
+ String rawMetadata = new String(config.getRawMetadata(), StandardCharsets.UTF_8);
+ return new StringBuilder()
+ .append(BluetoothBroadcastUtils.PREFIX_BTCC_AUDIO_LOCATION)
+ .append(METADATA_START).append(audioLocation).append(METADATA_END)
+ .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+ .append(BluetoothBroadcastUtils.PREFIX_BTCC_RAW_METADATA)
+ .append(METADATA_START).append(rawMetadata).append(METADATA_END)
+ .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+ .toString();
+ }
+
+ private String convertAudioContentToString(BluetoothLeAudioContentMetadata audioContent) {
+ String rawMetadata = new String(audioContent.getRawMetadata(), StandardCharsets.UTF_8);
+ return new StringBuilder()
+ .append(BluetoothBroadcastUtils.PREFIX_BTAC_PROGRAM_INFO)
+ .append(METADATA_START).append(audioContent.getProgramInfo()).append(METADATA_END)
+ .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+ .append(BluetoothBroadcastUtils.PREFIX_BTAC_LANGUAGE)
+ .append(METADATA_START).append(audioContent.getLanguage()).append(METADATA_END)
+ .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+ .append(BluetoothBroadcastUtils.PREFIX_BTAC_RAW_METADATA)
+ .append(METADATA_START).append(rawMetadata).append(METADATA_END)
+ .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+ .toString();
+ }
+
+ private String convertChannelToString(List<BluetoothLeBroadcastChannel> channelList) {
+ StringBuilder channelListBuilder = new StringBuilder();
+ String channelString = "";
+ for (BluetoothLeBroadcastChannel channel: channelList) {
+ String channelAudioCodec = convertAudioCodecConfigToString(channel.getCodecMetadata());
+ channelString = new StringBuilder()
+ .append(BluetoothBroadcastUtils.PREFIX_BTBC_CHANNEL_INDEX)
+ .append(METADATA_START).append(channel.getChannelIndex()).append(METADATA_END)
+ .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+ .append(BluetoothBroadcastUtils.PREFIX_BTBC_CODEC_CONFIG)
+ .append(METADATA_START).append(channelAudioCodec).append(METADATA_END)
+ .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE)
+ .toString();
+ channelListBuilder.append(channelString);
+ }
+ return channelListBuilder.toString();
+ }
+
/**
* Example : prefix is with the “BT:”, and end by the Android Version.
* BT:T:<1>;D:<00:11:22:AA:BB:CC>;AS:<1>;B:…;V:T;;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
index 298ee90d311d..bef1d9cbcde1 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
@@ -40,7 +40,6 @@ import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import java.util.Collection;
-import java.util.HashMap;
import java.util.Map;
@RunWith(RobolectricTestRunner.class)
@@ -503,8 +502,8 @@ public class CachedBluetoothDeviceManagerTest {
CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
CachedBluetoothDevice cachedDevice3 = mCachedDeviceManager.addDevice(mDevice3);
- cachedDevice1.setMemberDevice(cachedDevice2);
- cachedDevice1.setMemberDevice(cachedDevice3);
+ cachedDevice1.addMemberDevice(cachedDevice2);
+ cachedDevice1.addMemberDevice(cachedDevice3);
assertThat(cachedDevice1.getMemberDevice()).contains(cachedDevice2);
assertThat(cachedDevice1.getMemberDevice()).contains(cachedDevice3);
@@ -524,7 +523,7 @@ public class CachedBluetoothDeviceManagerTest {
CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
cachedDevice1.setGroupId(1);
cachedDevice2.setGroupId(1);
- cachedDevice1.setMemberDevice(cachedDevice2);
+ cachedDevice1.addMemberDevice(cachedDevice2);
// Call onDeviceUnpaired for the one in mCachedDevices.
mCachedDeviceManager.onDeviceUnpaired(cachedDevice1);
@@ -541,7 +540,7 @@ public class CachedBluetoothDeviceManagerTest {
CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
cachedDevice1.setGroupId(1);
cachedDevice2.setGroupId(1);
- cachedDevice1.setMemberDevice(cachedDevice2);
+ cachedDevice1.addMemberDevice(cachedDevice2);
// Call onDeviceUnpaired for the one in mCachedDevices.
mCachedDeviceManager.onDeviceUnpaired(cachedDevice2);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
index 61a28aab061f..9abb27e68398 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
@@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.view.LayoutInflater;
+import android.view.View;
import android.widget.TextView;
import androidx.preference.PreferenceViewHolder;
@@ -61,7 +62,7 @@ public class FooterPreferenceTest {
mFooterPreference.onBindViewHolder(holder);
assertThat(((TextView) holder.findViewById(
- R.id.settingslib_learn_more)).getText().toString())
+ R.id.settingslib_learn_more)).getText().toString())
.isEqualTo("Custom learn more");
}
@@ -86,4 +87,11 @@ public class FooterPreferenceTest {
assertThat(mFooterPreference.mLearnMoreListener).isNotNull();
}
+
+ @Test
+ public void setIconVisibility_shouldReturnSameVisibilityType() {
+ mFooterPreference.setIconVisibility(View.GONE);
+
+ assertThat(mFooterPreference.mIconVisibility).isEqualTo(View.GONE);
+ }
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 3029781f3e99..5eaf553a2047 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -120,6 +120,9 @@ public class SecureSettings {
Settings.Secure.ACTIVE_UNLOCK_ON_WAKE,
Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT,
Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
+ Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS,
+ Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO,
+ Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
Settings.Secure.VR_DISPLAY_MODE,
Settings.Secure.NOTIFICATION_BADGING,
Settings.Secure.NOTIFICATION_DISMISS_RTL,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index a4da49713f87..9ee7b654046f 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -18,6 +18,7 @@ package android.provider.settings.validators;
import static android.provider.settings.validators.SettingsValidators.ACCESSIBILITY_SHORTCUT_TARGET_LIST_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.ANY_INTEGER_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.COLON_SEPARATED_COMPONENT_LIST_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.COLON_SEPARATED_PACKAGE_LIST_VALIDATOR;
@@ -176,6 +177,10 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_WAKE, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS, ANY_STRING_VALIDATOR);
+ VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO, ANY_STRING_VALIDATOR);
+ VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
+ ANY_STRING_VALIDATOR);
VALIDATORS.put(Secure.ASSIST_GESTURE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ASSIST_GESTURE_SILENCE_ALERTS_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ASSIST_GESTURE_WAKE_ENABLED, BOOLEAN_VALIDATOR);
diff --git a/packages/SystemUI/res-keyguard/values-sw600dp-land/dimens.xml b/packages/SystemUI/res-keyguard/values-sw600dp-land/dimens.xml
index 96949995670f..c4baa52a6add 100644
--- a/packages/SystemUI/res-keyguard/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-sw600dp-land/dimens.xml
@@ -22,6 +22,4 @@
<!-- Overload default clock widget parameters -->
<dimen name="widget_big_font_size">88dp</dimen>
- <dimen name="qs_panel_padding_top">16dp</dimen>
-
-</resources> \ No newline at end of file
+</resources>
diff --git a/packages/SystemUI/res-product/values/strings.xml b/packages/SystemUI/res-product/values/strings.xml
index c1e81bacbec5..b71caef3f593 100644
--- a/packages/SystemUI/res-product/values/strings.xml
+++ b/packages/SystemUI/res-product/values/strings.xml
@@ -130,4 +130,8 @@
<!-- Text shown when viewing global actions while phone is locked and additional controls are hidden [CHAR LIMIT=NONE] -->
<string name="global_action_lock_message" product="device">Unlock your device for more options</string>
+ <!-- Text informing the user that their media is now playing on this device. [CHAR LIMIT=50] -->
+ <string name="media_transfer_playing_this_device" product="default">Playing on this phone</string>
+ <!-- Text informing the user that their media is now playing on this tablet device. [CHAR LIMIT=50] -->
+ <string name="media_transfer_playing_this_device" product="tablet">Playing on this tablet</string>
</resources>
diff --git a/packages/SystemUI/res/layout/alert_dialog_systemui.xml b/packages/SystemUI/res/layout/alert_dialog_systemui.xml
index 528f603c6a83..fd0623812745 100644
--- a/packages/SystemUI/res/layout/alert_dialog_systemui.xml
+++ b/packages/SystemUI/res/layout/alert_dialog_systemui.xml
@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-
<!--
- ~ Copyright (C) 2021 The Android Open Source Project
+ ~ 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.
@@ -15,88 +14,83 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<androidx.core.widget.NestedScrollView
+
+<com.android.internal.widget.AlertDialogLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@*android:id/parentPanel"
android:layout_width="match_parent"
- android:layout_height="wrap_content">
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal|top"
+ android:orientation="vertical"
+ android:paddingTop="@dimen/dialog_top_padding"
+>
+
+ <include layout="@layout/alert_dialog_title_systemui" />
- <com.android.internal.widget.AlertDialogLayout
- android:id="@*android:id/parentPanel"
+ <FrameLayout
+ android:id="@*android:id/contentPanel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:gravity="center_horizontal|top"
- android:orientation="vertical"
- android:paddingTop="@dimen/dialog_top_padding"
- >
+ android:minHeight="48dp"
+ android:paddingStart="@dimen/dialog_side_padding"
+ android:paddingEnd="@dimen/dialog_side_padding"
+ >
- <include layout="@layout/alert_dialog_title_systemui" />
-
- <FrameLayout
- android:id="@*android:id/contentPanel"
+ <ScrollView
+ android:id="@*android:id/scrollView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:minHeight="48dp"
- android:paddingStart="@dimen/dialog_side_padding"
- android:paddingEnd="@dimen/dialog_side_padding"
- >
+ android:clipToPadding="false">
- <ScrollView
- android:id="@*android:id/scrollView"
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:clipToPadding="false">
+ android:orientation="vertical">
- <LinearLayout
+ <Space
+ android:id="@*android:id/textSpacerNoTitle"
+ android:visibility="gone"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
+ android:layout_height="0dp" />
- <Space
- android:id="@*android:id/textSpacerNoTitle"
- android:visibility="gone"
- android:layout_width="match_parent"
- android:layout_height="0dp" />
+ <TextView
+ android:id="@*android:id/message"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/TextAppearance.Dialog.Body.Message" />
- <TextView
- android:id="@*android:id/message"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.Dialog.Body.Message" />
+ <Space
+ android:id="@*android:id/textSpacerNoButtons"
+ android:visibility="gone"
+ android:layout_width="match_parent"
+ android:layout_height="6dp" />
+ </LinearLayout>
+ </ScrollView>
+ </FrameLayout>
- <Space
- android:id="@*android:id/textSpacerNoButtons"
- android:visibility="gone"
- android:layout_width="match_parent"
- android:layout_height="6dp" />
- </LinearLayout>
- </ScrollView>
- </FrameLayout>
+ <FrameLayout
+ android:id="@*android:id/customPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="48dp"
+ android:paddingStart="@dimen/dialog_side_padding"
+ android:paddingEnd="@dimen/dialog_side_padding"
+ >
<FrameLayout
- android:id="@*android:id/customPanel"
+ android:id="@*android:id/custom"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:minHeight="48dp"
- android:paddingStart="@dimen/dialog_side_padding"
- android:paddingEnd="@dimen/dialog_side_padding"
- >
+ android:layout_height="wrap_content" />
+ </FrameLayout>
- <FrameLayout
- android:id="@*android:id/custom"
- android:layout_width="match_parent"
- android:layout_height="wrap_content" />
- </FrameLayout>
-
- <FrameLayout
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="@dimen/dialog_side_padding"
+ android:paddingEnd="@dimen/dialog_side_padding">
+ <include
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingStart="@dimen/dialog_side_padding"
- android:paddingEnd="@dimen/dialog_side_padding">
- <include
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- layout="@layout/alert_dialog_button_bar_systemui" />
- </FrameLayout>
- </com.android.internal.widget.AlertDialogLayout>
-
-</androidx.core.widget.NestedScrollView> \ No newline at end of file
+ layout="@layout/alert_dialog_button_bar_systemui" />
+ </FrameLayout>
+</com.android.internal.widget.AlertDialogLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index b230438f66fd..10bb6cbb95aa 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -121,6 +121,30 @@
android:adjustViewBounds="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
+ <TextView
+ android:id="@+id/hidden_text_preview"
+ android:visibility="gone"
+ android:textFontWeight="500"
+ android:padding="8dp"
+ android:gravity="center"
+ android:textSize="14sp"
+ android:text="@string/clipboard_text_hidden"
+ android:textColor="?attr/overlayButtonTextColor"
+ android:background="?androidprv:attr/colorAccentSecondary"
+ android:layout_width="@dimen/clipboard_preview_size"
+ android:layout_height="@dimen/clipboard_preview_size"/>
+ <TextView
+ android:id="@+id/hidden_image_preview"
+ android:visibility="gone"
+ android:textFontWeight="500"
+ android:padding="8dp"
+ android:gravity="center"
+ android:textSize="14sp"
+ android:text="@string/clipboard_text_hidden"
+ android:textColor="#ffffff"
+ android:background="#000000"
+ android:layout_width="@dimen/clipboard_preview_size"
+ android:layout_height="@dimen/clipboard_preview_size"/>
</FrameLayout>
<FrameLayout
android:id="@+id/dismiss_button"
@@ -141,4 +165,4 @@
android:layout_margin="@dimen/overlay_dismiss_button_margin"
android:src="@drawable/overlay_cancel"/>
</FrameLayout>
-</com.android.systemui.screenshot.DraggableConstraintLayout>
+</com.android.systemui.screenshot.DraggableConstraintLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/fgs_manager_app_item.xml b/packages/SystemUI/res/layout/fgs_manager_app_item.xml
index 30bce9d84489..a6f659d049e4 100644
--- a/packages/SystemUI/res/layout/fgs_manager_app_item.xml
+++ b/packages/SystemUI/res/layout/fgs_manager_app_item.xml
@@ -50,7 +50,8 @@
<Button
android:id="@+id/fgs_manager_app_item_stop_button"
android:layout_width="wrap_content"
- android:layout_height="48dp"
+ android:layout_height="wrap_content"
+ android:minHeight="48dp"
android:text="@string/fgs_manager_app_item_stop_button_label"
android:layout_marginStart="12dp"
style="?android:attr/buttonBarNeutralButtonStyle" />
diff --git a/packages/SystemUI/res/layout/qs_paged_page.xml b/packages/SystemUI/res/layout/qs_paged_page.xml
index 98804fc80b50..c366ceb4c190 100644
--- a/packages/SystemUI/res/layout/qs_paged_page.xml
+++ b/packages/SystemUI/res/layout/qs_paged_page.xml
@@ -19,7 +19,7 @@
android:id="@+id/tile_page"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:paddingStart="@dimen/notification_side_paddings"
- android:paddingEnd="@dimen/notification_side_paddings"
+ android:paddingStart="@dimen/qs_tiles_page_horizontal_margin"
+ android:paddingEnd="@dimen/qs_tiles_page_horizontal_margin"
android:clipChildren="false"
android:clipToPadding="false" />
diff --git a/packages/SystemUI/res/layout/scrollable_alert_dialog_systemui.xml b/packages/SystemUI/res/layout/scrollable_alert_dialog_systemui.xml
new file mode 100644
index 000000000000..71bb938dfdf4
--- /dev/null
+++ b/packages/SystemUI/res/layout/scrollable_alert_dialog_systemui.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<androidx.core.widget.NestedScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <include layout="@layout/alert_dialog_systemui" />
+
+</androidx.core.widget.NestedScrollView> \ No newline at end of file
diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
index b3da144ac73b..6c42073b8805 100644
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -21,13 +21,20 @@
<dimen name="keyguard_indication_margin_bottom">25dp</dimen>
<dimen name="ambient_indication_margin_bottom">115dp</dimen>
<dimen name="lock_icon_margin_bottom">60dp</dimen>
-
- <dimen name="qs_media_session_height_expanded">172dp</dimen>
-
<!-- margin from keyguard status bar to clock. For split shade it should be
keyguard_split_shade_top_margin - status_bar_header_height_keyguard = 8dp -->
<dimen name="keyguard_clock_top_margin">8dp</dimen>
+ <!-- QS-->
+ <dimen name="qs_panel_padding_top">16dp</dimen>
+ <dimen name="qs_content_horizontal_padding">24dp</dimen>
+ <dimen name="qs_horizontal_margin">24dp</dimen>
+ <!-- in split shade qs_tiles_page_horizontal_margin should be equal of qs_horizontal_margin/2,
+ otherwise full space between two pages is qs_horizontal_margin*2, and that makes tiles page
+ not appear immediately after user swipes to the side -->
+ <dimen name="qs_tiles_page_horizontal_margin">12dp</dimen>
+ <dimen name="qs_media_session_height_expanded">172dp</dimen>
+
<dimen name="split_shade_notifications_scrim_margin_bottom">16dp</dimen>
<dimen name="notification_panel_margin_bottom">48dp</dimen>
diff --git a/packages/SystemUI/res/values-sw720dp-land/dimens.xml b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
index 33d6f19b35d0..f45f106d391d 100644
--- a/packages/SystemUI/res/values-sw720dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
@@ -26,6 +26,12 @@
<dimen name="status_bar_header_height_keyguard">56dp</dimen>
<dimen name="qs_media_session_height_expanded">251dp</dimen>
+ <dimen name="qs_content_horizontal_padding">40dp</dimen>
+ <dimen name="qs_horizontal_margin">40dp</dimen>
+ <!-- in split shade qs_tiles_page_horizontal_margin should be equal of qs_horizontal_margin/2,
+ otherwise full space between two pages is qs_horizontal_margin*2, and that makes tiles page
+ not appear immediately after user swipes to the side -->
+ <dimen name="qs_tiles_page_horizontal_margin">20dp</dimen>
<dimen name="lockscreen_shade_max_over_scroll_amount">42dp</dimen>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index f77430bae4ab..a014efb7d176 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -478,6 +478,22 @@
<dimen name="qqs_layout_margin_top">16dp</dimen>
<dimen name="qqs_layout_padding_bottom">24dp</dimen>
+ <!-- Most of the time it should be the same as notification_side_paddings as it's vertically
+ aligned with notifications. The exception is split shade when this value becomes
+ independent -->
+ <dimen name="qs_horizontal_margin">@dimen/notification_side_paddings</dimen>
+
+ <!-- Most of the time it should be the same as notification_shade_content_margin_horizontal as
+ it's vertically aligned with notifications. The exception is split shade when this value
+ becomes independent -->
+ <dimen name="qs_content_horizontal_padding">@dimen/notification_shade_content_margin_horizontal</dimen>
+
+ <!-- Most of the time it should be the same as notification_side_paddings as it's vertically
+ aligned with notifications. That's not the case on large screen when we have either split
+ shade and QS is not above notifications or in portrait shade when notification scrim is no
+ longer full width and next page of tiles should be at the edge of the screen -->
+ <dimen name="qs_tiles_page_horizontal_margin">@dimen/notification_side_paddings</dimen>
+
<dimen name="qs_customize_internal_side_paddings">8dp</dimen>
<dimen name="qs_icon_size">20dp</dimen>
<dimen name="qs_side_view_size">28dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8f412e342fa8..2426f017e20e 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1211,7 +1211,7 @@
<string name="wallet_app_button_label">Show all</string>
<!-- Label of the button underneath the card carousel prompting user unlock device. [CHAR LIMIT=NONE] -->
<!-- Secondary label of the quick access wallet tile if no card. [CHAR LIMIT=NONE] -->
- <string name="wallet_secondary_label_no_card">Add a card</string>
+ <string name="wallet_secondary_label_no_card">Tap to open</string>
<!-- Secondary label of the quick access wallet tile if wallet is still updating. [CHAR LIMIT=NONE] -->
<string name="wallet_secondary_label_updating">Updating</string>
<!-- Secondary label of the quick access wallet tile if device locked. [CHAR LIMIT=NONE] -->
@@ -2229,11 +2229,7 @@
<string name="media_move_closer_to_end_cast">Move closer to <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g> to play here</string>
<!-- Text informing the user that their media is now playing on a different device (deviceName). [CHAR LIMIT=50] -->
<string name="media_transfer_playing_different_device">Playing on <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g></string>
- <!-- Text informing the user that their media is now playing on this device. [CHAR LIMIT=50] -->
- <string name="media_transfer_playing_this_device" product="default">Playing on this phone</string>
- <!-- Text informing the user that their media is now playing on this tablet device. [CHAR LIMIT=50] -->
- <string name="media_transfer_playing_this_device" product="tablet">Playing on this tablet</string>
- <!-- Text informing the user that the media transfer has failed because something went wrong. [CHAR LIMIT=50] -->
+ <!-- Text informing the user that the media transfer has failed because something went wrong. [CHAR LIsMIT=50] -->
<string name="media_transfer_failed">Something went wrong. Try again.</string>
<!-- Error message indicating that a control timed out while waiting for an update [CHAR_LIMIT=30] -->
@@ -2486,6 +2482,8 @@
<string name="clipboard_edit_image_description">Edit copied image</string>
<!-- Label for button to send copied content to a nearby device [CHAR LIMIT=NONE] -->
<string name="clipboard_send_nearby_description">Send to nearby device</string>
+ <!-- Text informing user that copied content is hidden [CHAR LIMIT=NONE] -->
+ <string name="clipboard_text_hidden">Tap to view</string>
<!-- Generic "add" string [CHAR LIMIT=NONE] -->
<string name="add">Add</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index c93c0652a070..0c25f5473416 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -380,7 +380,7 @@
<item name="android:buttonBarNegativeButtonStyle">@style/Widget.Dialog.Button.BorderButton</item>
<item name="android:buttonBarNeutralButtonStyle">@style/Widget.Dialog.Button.BorderButton</item>
<item name="android:colorBackground">?androidprv:attr/colorSurface</item>
- <item name="android:alertDialogStyle">@style/AlertDialogStyle</item>
+ <item name="android:alertDialogStyle">@style/ScrollableAlertDialogStyle</item>
<item name="android:buttonBarStyle">@style/ButtonBarStyle</item>
<item name="android:buttonBarButtonStyle">@style/Widget.Dialog.Button.Large</item>
</style>
@@ -389,6 +389,10 @@
<item name="android:layout">@layout/alert_dialog_systemui</item>
</style>
+ <style name="ScrollableAlertDialogStyle" parent="@androidprv:style/AlertDialog.DeviceDefault">
+ <item name="android:layout">@layout/scrollable_alert_dialog_systemui</item>
+ </style>
+
<style name="ButtonBarStyle" parent="@androidprv:style/DeviceDefault.ButtonBar.AlertDialog">
<item name="android:paddingTop">@dimen/dialog_button_bar_top_padding</item>
<item name="android:paddingBottom">@dimen/dialog_bottom_padding</item>
@@ -994,6 +998,7 @@
<style name="Theme.SystemUI.Dialog.Cast">
<item name="android:textAppearanceMedium">@style/TextAppearance.CastItem</item>
+ <item name="android:alertDialogStyle">@style/AlertDialogStyle</item>
</style>
<!-- ************************************************************************************* -->
@@ -1111,8 +1116,9 @@
</style>
<style name="FgsManagerAppDuration">
- <item name="android:fontFamily">?android:attr/textAppearanceSmall</item>
<item name="android:textDirection">locale</item>
+ <item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
+ <item name="android:textColor">?android:attr/textColorSecondary</item>
</style>
<style name="BroadcastDialog">
diff --git a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
index f195d2094d6a..38fa35453418 100644
--- a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
@@ -16,14 +16,20 @@
package com.android.keyguard
+import android.annotation.IntDef
import android.content.ContentResolver
import android.database.ContentObserver
+import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_TIMEOUT
import android.net.Uri
import android.os.Handler
import android.os.UserHandle
import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL
+import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO
+import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS
import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT
+import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED
import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_WAKE
+import android.util.Log
import com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
@@ -44,6 +50,20 @@ class ActiveUnlockConfig @Inject constructor(
dumpManager: DumpManager
) : Dumpable {
+ companion object {
+ const val TAG = "ActiveUnlockConfig"
+
+ const val BIOMETRIC_TYPE_NONE = 0
+ const val BIOMETRIC_TYPE_ANY_FACE = 1
+ const val BIOMETRIC_TYPE_ANY_FINGERPRINT = 2
+ const val BIOMETRIC_TYPE_UNDER_DISPLAY_FINGERPRINT = 3
+ }
+
+ @Retention(AnnotationRetention.SOURCE)
+ @IntDef(BIOMETRIC_TYPE_NONE, BIOMETRIC_TYPE_ANY_FACE, BIOMETRIC_TYPE_ANY_FINGERPRINT,
+ BIOMETRIC_TYPE_UNDER_DISPLAY_FINGERPRINT)
+ annotation class BiometricType
+
/**
* Indicates the origin for an active unlock request.
*/
@@ -51,35 +71,50 @@ class ActiveUnlockConfig @Inject constructor(
WAKE, UNLOCK_INTENT, BIOMETRIC_FAIL, ASSISTANT
}
+ var keyguardUpdateMonitor: KeyguardUpdateMonitor? = null
private var requestActiveUnlockOnWakeup = false
private var requestActiveUnlockOnUnlockIntent = false
private var requestActiveUnlockOnBioFail = false
+ private var faceErrorsToTriggerBiometricFailOn = mutableSetOf(FACE_ERROR_TIMEOUT)
+ private var faceAcquireInfoToTriggerBiometricFailOn = mutableSetOf<Int>()
+ private var onUnlockIntentWhenBiometricEnrolled = mutableSetOf<Int>(BIOMETRIC_TYPE_NONE)
+
private val settingsObserver = object : ContentObserver(handler) {
- private val wakeUri: Uri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_WAKE)
- private val unlockIntentUri: Uri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT)
- private val bioFailUri: Uri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL)
+ private val wakeUri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_WAKE)
+ private val unlockIntentUri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT)
+ private val bioFailUri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL)
+ private val faceErrorsUri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ERRORS)
+ private val faceAcquireInfoUri =
+ secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO)
+ private val unlockIntentWhenBiometricEnrolledUri =
+ secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED)
fun register() {
- contentResolver.registerContentObserver(
- wakeUri,
- false,
- this,
- UserHandle.USER_ALL)
- contentResolver.registerContentObserver(
- unlockIntentUri,
- false,
- this,
- UserHandle.USER_ALL)
- contentResolver.registerContentObserver(
- bioFailUri,
- false,
- this,
- UserHandle.USER_ALL)
+ registerUri(
+ listOf(
+ wakeUri,
+ unlockIntentUri,
+ bioFailUri,
+ faceErrorsUri,
+ faceAcquireInfoUri,
+ unlockIntentWhenBiometricEnrolledUri
+ )
+ )
onChange(true, ArrayList(), 0, getCurrentUser())
}
+ private fun registerUri(uris: Collection<Uri>) {
+ for (uri in uris) {
+ contentResolver.registerContentObserver(
+ uri,
+ false,
+ this,
+ UserHandle.USER_ALL)
+ }
+ }
+
override fun onChange(
selfChange: Boolean,
uris: Collection<Uri>,
@@ -104,6 +139,55 @@ class ActiveUnlockConfig @Inject constructor(
requestActiveUnlockOnBioFail = secureSettings.getIntForUser(
ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 0, getCurrentUser()) == 1
}
+
+ if (selfChange || uris.contains(faceErrorsUri)) {
+ processStringArray(
+ secureSettings.getStringForUser(ACTIVE_UNLOCK_ON_FACE_ERRORS,
+ getCurrentUser()),
+ faceErrorsToTriggerBiometricFailOn,
+ setOf(FACE_ERROR_TIMEOUT))
+ }
+
+ if (selfChange || uris.contains(faceAcquireInfoUri)) {
+ processStringArray(
+ secureSettings.getStringForUser(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO,
+ getCurrentUser()),
+ faceAcquireInfoToTriggerBiometricFailOn,
+ setOf<Int>())
+ }
+
+ if (selfChange || uris.contains(unlockIntentWhenBiometricEnrolledUri)) {
+ processStringArray(
+ secureSettings.getStringForUser(
+ ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
+ getCurrentUser()),
+ onUnlockIntentWhenBiometricEnrolled,
+ setOf(BIOMETRIC_TYPE_NONE))
+ }
+ }
+
+ /**
+ * Convert a pipe-separated set of integers into a set of ints.
+ * @param stringSetting expected input are integers delineated by a pipe. For example,
+ * it may look something like this: "1|5|3".
+ * @param out updates the "out" Set will the integers between the pipes.
+ * @param default If stringSetting is null, "out" will be populated with values in "default"
+ */
+ private fun processStringArray(
+ stringSetting: String?,
+ out: MutableSet<Int>,
+ default: Set<Int>
+ ) {
+ out.clear()
+ stringSetting?.let {
+ for (code: String in stringSetting.split("|")) {
+ try {
+ out.add(code.toInt())
+ } catch (e: NumberFormatException) {
+ Log.e(TAG, "Passed an invalid setting=$code")
+ }
+ }
+ } ?: out.addAll(default)
}
}
@@ -113,6 +197,30 @@ class ActiveUnlockConfig @Inject constructor(
}
/**
+ * If any active unlock triggers are enabled.
+ */
+ fun isActiveUnlockEnabled(): Boolean {
+ return requestActiveUnlockOnWakeup || requestActiveUnlockOnUnlockIntent ||
+ requestActiveUnlockOnBioFail
+ }
+
+ /**
+ * Whether the face error code from {@link BiometricFaceConstants} should trigger
+ * active unlock on biometric failure.
+ */
+ fun shouldRequestActiveUnlockOnFaceError(errorCode: Int): Boolean {
+ return faceErrorsToTriggerBiometricFailOn.contains(errorCode)
+ }
+
+ /**
+ * Whether the face acquireInfo from {@link BiometricFaceConstants} should trigger
+ * active unlock on biometric failure.
+ */
+ fun shouldRequestActiveUnlockOnFaceAcquireInfo(acquiredInfo: Int): Boolean {
+ return faceAcquireInfoToTriggerBiometricFailOn.contains(acquiredInfo)
+ }
+
+ /**
* Whether to trigger active unlock based on where the request is coming from and
* the current settings.
*/
@@ -121,7 +229,8 @@ class ActiveUnlockConfig @Inject constructor(
ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE -> requestActiveUnlockOnWakeup
ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT ->
- requestActiveUnlockOnUnlockIntent || requestActiveUnlockOnWakeup
+ requestActiveUnlockOnUnlockIntent || requestActiveUnlockOnWakeup ||
+ (shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment())
ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL ->
requestActiveUnlockOnBioFail || requestActiveUnlockOnUnlockIntent ||
@@ -131,17 +240,55 @@ class ActiveUnlockConfig @Inject constructor(
}
}
- /**
- * If any active unlock triggers are enabled.
- */
- fun isActiveUnlockEnabled(): Boolean {
- return requestActiveUnlockOnWakeup || requestActiveUnlockOnUnlockIntent ||
- requestActiveUnlockOnBioFail
+ private fun shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment(): Boolean {
+ if (!requestActiveUnlockOnBioFail) {
+ return false
+ }
+
+ keyguardUpdateMonitor?.let {
+ val anyFaceEnrolled = it.isFaceEnrolled
+ val anyFingerprintEnrolled =
+ it.getCachedIsUnlockWithFingerprintPossible(getCurrentUser())
+ val udfpsEnrolled = it.isUdfpsEnrolled
+
+ if (!anyFaceEnrolled && !anyFingerprintEnrolled) {
+ return onUnlockIntentWhenBiometricEnrolled.contains(BIOMETRIC_TYPE_NONE)
+ }
+
+ if (!anyFaceEnrolled && anyFingerprintEnrolled) {
+ return onUnlockIntentWhenBiometricEnrolled.contains(
+ BIOMETRIC_TYPE_ANY_FINGERPRINT) ||
+ (udfpsEnrolled && onUnlockIntentWhenBiometricEnrolled.contains(
+ BIOMETRIC_TYPE_UNDER_DISPLAY_FINGERPRINT))
+ }
+
+ if (!anyFingerprintEnrolled && anyFaceEnrolled) {
+ return onUnlockIntentWhenBiometricEnrolled.contains(BIOMETRIC_TYPE_ANY_FACE)
+ }
+ }
+
+ return false
}
override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("Settings:")
pw.println(" requestActiveUnlockOnWakeup=$requestActiveUnlockOnWakeup")
pw.println(" requestActiveUnlockOnUnlockIntent=$requestActiveUnlockOnUnlockIntent")
pw.println(" requestActiveUnlockOnBioFail=$requestActiveUnlockOnBioFail")
+ pw.println(" requestActiveUnlockOnUnlockIntentWhenBiometricEnrolled=" +
+ "$onUnlockIntentWhenBiometricEnrolled")
+ pw.println(" requestActiveUnlockOnFaceError=$faceErrorsToTriggerBiometricFailOn")
+ pw.println(" requestActiveUnlockOnFaceAcquireInfo=" +
+ "$faceAcquireInfoToTriggerBiometricFailOn")
+
+ pw.println("Current state:")
+ keyguardUpdateMonitor?.let {
+ pw.println(" shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment=" +
+ "${shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment()}")
+ pw.println(" faceEnrolled=${it.isFaceEnrolled}")
+ pw.println(" fpEnrolled=${
+ it.getCachedIsUnlockWithFingerprintPossible(getCurrentUser())}")
+ pw.println(" udfpsEnrolled=${it.isUdfpsEnrolled}")
+ } ?: pw.println(" keyguardUpdateMonitor is uninitialized")
}
} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 6a68c70c6acb..98ac640bf703 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -28,7 +28,6 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -169,7 +168,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
private final EmergencyButtonController.Factory mEmergencyButtonControllerFactory;
private final FalsingCollector mFalsingCollector;
private final DevicePostureController mDevicePostureController;
- private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private final KeyguardViewController mKeyguardViewController;
@Inject
public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -181,7 +180,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
TelephonyManager telephonyManager, FalsingCollector falsingCollector,
EmergencyButtonController.Factory emergencyButtonControllerFactory,
DevicePostureController devicePostureController,
- StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
+ KeyguardViewController keyguardViewController) {
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
mLatencyTracker = latencyTracker;
@@ -194,7 +193,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
mEmergencyButtonControllerFactory = emergencyButtonControllerFactory;
mFalsingCollector = falsingCollector;
mDevicePostureController = devicePostureController;
- mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ mKeyguardViewController = keyguardViewController;
}
/** Create a new {@link KeyguardInputViewController}. */
@@ -215,7 +214,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
mInputMethodManager, emergencyButtonController, mMainExecutor, mResources,
- mFalsingCollector, mStatusBarKeyguardViewManager);
+ mFalsingCollector, mKeyguardViewController);
} else if (keyguardInputView instanceof KeyguardPINView) {
return new KeyguardPinViewController((KeyguardPINView) keyguardInputView,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 19035264db7f..8f44e97ef231 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -43,7 +43,6 @@ import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.util.concurrency.DelayableExecutor;
import java.util.List;
@@ -56,7 +55,7 @@ public class KeyguardPasswordViewController
private final KeyguardSecurityCallback mKeyguardSecurityCallback;
private final InputMethodManager mInputMethodManager;
private final DelayableExecutor mMainExecutor;
- private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private final KeyguardViewController mKeyguardViewController;
private final boolean mShowImeAtScreenOn;
private EditText mPasswordEntry;
private ImageView mSwitchImeButton;
@@ -119,14 +118,14 @@ public class KeyguardPasswordViewController
@Main DelayableExecutor mainExecutor,
@Main Resources resources,
FalsingCollector falsingCollector,
- StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
+ KeyguardViewController keyguardViewController) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, falsingCollector,
emergencyButtonController);
mKeyguardSecurityCallback = keyguardSecurityCallback;
mInputMethodManager = inputMethodManager;
mMainExecutor = mainExecutor;
- mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ mKeyguardViewController = keyguardViewController;
mShowImeAtScreenOn = resources.getBoolean(R.bool.kg_show_ime_at_screen_on);
mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
mSwitchImeButton = mView.findViewById(R.id.switch_ime_button);
@@ -209,7 +208,7 @@ public class KeyguardPasswordViewController
}
private void showInput() {
- if (!mStatusBarKeyguardViewManager.isBouncerShowing()) {
+ if (!mKeyguardViewController.isBouncerShowing()) {
return;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 41f92407a683..39c394981193 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -282,7 +282,7 @@ public class KeyguardPatternViewController
super.reloadColors();
mMessageAreaController.reloadColors();
int textColor = Utils.getColorAttr(mLockPatternView.getContext(),
- android.R.attr.textColorPrimary).getDefaultColor();
+ android.R.attr.textColorSecondary).getDefaultColor();
int errorColor = Utils.getColorError(mLockPatternView.getContext()).getDefaultColor();
mLockPatternView.setColors(textColor, textColor, errorColor);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index bbe9a362b1fa..121ac299ec5b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -56,7 +56,6 @@ import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.hardware.SensorPrivacyManager;
-import android.hardware.biometrics.BiometricFaceConstants;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricSourceType;
@@ -1615,7 +1614,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mKeyguardBypassController.setUserHasDeviceEntryIntent(false);
}
- if (errMsgId == BiometricFaceConstants.FACE_ERROR_TIMEOUT) {
+ if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceError(errMsgId)) {
requestActiveUnlock(
ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL,
"faceError-" + errMsgId);
@@ -1625,6 +1624,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
@Override
public void onAuthenticationAcquired(int acquireInfo) {
handleFaceAcquired(acquireInfo);
+
+ if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
+ acquireInfo)) {
+ requestActiveUnlock(
+ ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL,
+ "faceAcquireInfo-" + acquireInfo);
+ }
}
};
@@ -1639,6 +1645,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private boolean mFingerprintLockedOut;
private boolean mFingerprintLockedOutPermanent;
private boolean mFaceLockedOutPermanent;
+ private HashMap<Integer, Boolean> mIsUnlockWithFingerprintPossible = new HashMap<>();
private TelephonyManager mTelephonyManager;
/**
@@ -1889,6 +1896,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
dumpManager.registerDumpable(getClass().getName(), this);
mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
mActiveUnlockConfig = activeUnlockConfiguration;
+ mActiveUnlockConfig.setKeyguardUpdateMonitor(this);
mHandler = new Handler(mainLooper) {
@Override
@@ -2329,7 +2337,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
if (shouldTriggerActiveUnlock()) {
- if (DEBUG) {
+ if (DEBUG_ACTIVE_UNLOCK) {
Log.d("ActiveUnlock", "initiate active unlock triggerReason=" + reason);
}
mTrustManager.reportUserMayRequestUnlock(KeyguardUpdateMonitor.getCurrentUser());
@@ -2359,7 +2367,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
if (allowRequest && shouldTriggerActiveUnlock()) {
- if (DEBUG) {
+ if (DEBUG_ACTIVE_UNLOCK) {
Log.d("ActiveUnlock", "reportUserRequestedUnlock"
+ " origin=" + requestOrigin.name()
+ " reason=" + reason
@@ -2777,8 +2785,17 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
private boolean isUnlockWithFingerprintPossible(int userId) {
- return mFpm != null && mFpm.isHardwareDetected() && !isFingerprintDisabled(userId)
- && mFpm.hasEnrolledTemplates(userId);
+ mIsUnlockWithFingerprintPossible.put(userId, mFpm != null && mFpm.isHardwareDetected()
+ && !isFingerprintDisabled(userId) && mFpm.hasEnrolledTemplates(userId));
+ return mIsUnlockWithFingerprintPossible.get(userId);
+ }
+
+ /**
+ * Cached value for whether fingerprint is enrolled and possible to use for authentication.
+ * Note: checking fingerprint enrollment directly with the AuthController requires an IPC.
+ */
+ public boolean getCachedIsUnlockWithFingerprintPossible(int userId) {
+ return mIsUnlockWithFingerprintPossible.get(userId);
}
private boolean isUnlockWithFacePossible(int userId) {
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index e1913657b7cc..fdde40296511 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -55,7 +55,7 @@ public class LockIconView extends FrameLayout implements Dumpable {
@NonNull private final RectF mSensorRect;
@NonNull private PointF mLockIconCenter = new PointF(0f, 0f);
- private int mRadius;
+ private float mRadius;
private int mLockIconPadding;
private ImageView mLockIcon;
@@ -126,7 +126,7 @@ public class LockIconView extends FrameLayout implements Dumpable {
* Set the location of the lock icon.
*/
@VisibleForTesting
- public void setCenterLocation(@NonNull PointF center, int radius, int drawablePadding) {
+ public void setCenterLocation(@NonNull PointF center, float radius, int drawablePadding) {
mLockIconCenter = center;
mRadius = radius;
mLockIconPadding = drawablePadding;
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 2b217f02e834..d79b1454514e 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -188,6 +188,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
protected void onViewAttached() {
updateIsUdfpsEnrolled();
updateConfiguration();
+ updateLockIconLocation();
updateKeyguardShowing();
mUserUnlockedWithBiometric = false;
@@ -340,20 +341,17 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
mHeightPixels = bounds.bottom;
mBottomPaddingPx = getResources().getDimensionPixelSize(R.dimen.lock_icon_margin_bottom);
- final int defaultPaddingPx =
- getResources().getDimensionPixelSize(R.dimen.lock_icon_padding);
- mScaledPaddingPx = (int) (defaultPaddingPx * mAuthController.getScaleFactor());
-
mUnlockedLabel = mView.getContext().getResources().getString(
R.string.accessibility_unlock_button);
mLockedLabel = mView.getContext()
.getResources().getString(R.string.accessibility_lock_icon);
-
- updateLockIconLocation();
}
private void updateLockIconLocation() {
if (mUdfpsSupported) {
+ final int defaultPaddingPx =
+ getResources().getDimensionPixelSize(R.dimen.lock_icon_padding);
+ mScaledPaddingPx = (int) (defaultPaddingPx * mAuthController.getScaleFactor());
mView.setCenterLocation(mAuthController.getUdfpsLocation(),
mAuthController.getUdfpsRadius(), mScaledPaddingPx);
} else {
@@ -362,8 +360,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
mHeightPixels - mBottomPaddingPx - sLockIconRadiusPx),
sLockIconRadiusPx, mScaledPaddingPx);
}
-
- mView.getHitRect(mSensorTouchLocation);
}
@Override
@@ -386,6 +382,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
pw.println(" mCanDismissLockScreen: " + mCanDismissLockScreen);
pw.println(" mStatusBarState: " + StatusBarState.toString(mStatusBarState));
pw.println(" mInterpolatedDarkAmount: " + mInterpolatedDarkAmount);
+ pw.println(" mSensorTouchLocation: " + mSensorTouchLocation);
if (mView != null) {
mView.dump(pw, args);
@@ -672,6 +669,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
}
private boolean inLockIconArea(MotionEvent event) {
+ mView.getHitRect(mSensorTouchLocation);
return mSensorTouchLocation.contains((int) event.getX(), (int) event.getY())
&& mView.getVisibility() == View.VISIBLE;
}
@@ -692,6 +690,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
mExecutor.execute(() -> {
updateIsUdfpsEnrolled();
updateConfiguration();
+ updateLockIconLocation();
});
}
@@ -705,6 +704,11 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
public void onEnrollmentsChanged() {
updateUdfpsConfig();
}
+
+ @Override
+ public void onUdfpsLocationChanged() {
+ updateLockIconLocation();
+ }
};
private final View.OnClickListener mA11yClickListener = v -> onLongPress();
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
index caf7ee4db2c3..c91c8992780c 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
@@ -30,7 +30,6 @@ import android.widget.TextView;
import androidx.annotation.StyleRes;
-import com.android.settingslib.Utils;
import com.android.systemui.animation.Interpolators;
/**
@@ -42,24 +41,29 @@ class NumPadAnimator {
private ValueAnimator mContractAnimator;
private AnimatorSet mContractAnimatorSet;
private GradientDrawable mBackground;
- private int mNormalColor;
- private int mHighlightColor;
- private int mStyle;
+ private Drawable mImageButton;
private TextView mDigitTextView;
+ private int mNormalBackgroundColor;
+ private int mPressedBackgroundColor;
+ private int mTextColorPrimary;
+ private int mTextColorPressed;
+ private int mStyle;
private static final int EXPAND_ANIMATION_MS = 100;
private static final int EXPAND_COLOR_ANIMATION_MS = 50;
private static final int CONTRACT_ANIMATION_DELAY_MS = 33;
private static final int CONTRACT_ANIMATION_MS = 417;
- NumPadAnimator(Context context, final Drawable drawable, @StyleRes int style) {
- this(context, drawable, style, null);
+ NumPadAnimator(Context context, final Drawable drawable,
+ @StyleRes int style, Drawable buttonImage) {
+ this(context, drawable, style, null, buttonImage);
}
NumPadAnimator(Context context, final Drawable drawable, @StyleRes int style,
- @Nullable TextView digitTextView) {
+ @Nullable TextView digitTextView, @Nullable Drawable buttonImage) {
mStyle = style;
mBackground = (GradientDrawable) drawable;
mDigitTextView = digitTextView;
+ mImageButton = buttonImage;
reloadColors(context);
}
@@ -88,26 +92,28 @@ class NumPadAnimator {
* Reload colors from resources.
**/
void reloadColors(Context context) {
- int[] customAttrs = {android.R.attr.colorControlNormal,
- android.R.attr.colorControlHighlight};
+ boolean isNumPadKey = mImageButton == null;
+ int[] customAttrs = {android.R.attr.colorControlNormal};
ContextThemeWrapper ctw = new ContextThemeWrapper(context, mStyle);
TypedArray a = ctw.obtainStyledAttributes(customAttrs);
- mNormalColor = getPrivateAttrColorIfUnset(ctw, a, 0, 0,
+ mNormalBackgroundColor = getPrivateAttrColorIfUnset(ctw, a, 0, 0,
com.android.internal.R.attr.colorSurface);
- mHighlightColor = a.getColor(1, 0);
a.recycle();
-
- mBackground.setColor(mNormalColor);
- createAnimators(context);
+ mBackground.setColor(mNormalBackgroundColor);
+
+ mPressedBackgroundColor = context.getColor(android.R.color.system_accent1_200);
+ mTextColorPrimary = isNumPadKey
+ ? com.android.settingslib.Utils
+ .getColorAttrDefaultColor(context, android.R.attr.textColorPrimary)
+ : com.android.settingslib.Utils
+ .getColorAttrDefaultColor(context, android.R.attr.textColorPrimaryInverse);
+ mTextColorPressed = com.android.settingslib.Utils
+ .getColorAttrDefaultColor(context, com.android.internal.R.attr.textColorOnAccent);
+ createAnimators();
}
- private void createAnimators(Context context) {
- int textColorPrimary = Utils.getColorAttrDefaultColor(context,
- android.R.attr.textColorPrimary);
- int textColorPrimaryInverse = Utils.getColorAttrDefaultColor(context,
- android.R.attr.textColorPrimaryInverse);
-
+ private void createAnimators() {
// Actual values will be updated later, usually during an onLayout() call
mExpandAnimator = ValueAnimator.ofFloat(0f, 1f);
mExpandAnimator.setDuration(EXPAND_ANIMATION_MS);
@@ -116,7 +122,7 @@ class NumPadAnimator {
anim -> mBackground.setCornerRadius((float) anim.getAnimatedValue()));
ValueAnimator expandBackgroundColorAnimator = ValueAnimator.ofObject(new ArgbEvaluator(),
- mNormalColor, mHighlightColor);
+ mNormalBackgroundColor, mPressedBackgroundColor);
expandBackgroundColorAnimator.setDuration(EXPAND_COLOR_ANIMATION_MS);
expandBackgroundColorAnimator.setInterpolator(Interpolators.LINEAR);
expandBackgroundColorAnimator.addUpdateListener(
@@ -124,13 +130,16 @@ class NumPadAnimator {
ValueAnimator expandTextColorAnimator =
ValueAnimator.ofObject(new ArgbEvaluator(),
- textColorPrimary, textColorPrimaryInverse);
+ mTextColorPrimary, mTextColorPressed);
expandTextColorAnimator.setInterpolator(Interpolators.LINEAR);
expandTextColorAnimator.setDuration(EXPAND_COLOR_ANIMATION_MS);
expandTextColorAnimator.addUpdateListener(valueAnimator -> {
if (mDigitTextView != null) {
mDigitTextView.setTextColor((int) valueAnimator.getAnimatedValue());
}
+ if (mImageButton != null) {
+ mImageButton.setTint((int) valueAnimator.getAnimatedValue());
+ }
});
mExpandAnimatorSet = new AnimatorSet();
@@ -144,7 +153,7 @@ class NumPadAnimator {
mContractAnimator.addUpdateListener(
anim -> mBackground.setCornerRadius((float) anim.getAnimatedValue()));
ValueAnimator contractBackgroundColorAnimator = ValueAnimator.ofObject(new ArgbEvaluator(),
- mHighlightColor, mNormalColor);
+ mPressedBackgroundColor, mNormalBackgroundColor);
contractBackgroundColorAnimator.setInterpolator(Interpolators.LINEAR);
contractBackgroundColorAnimator.setStartDelay(CONTRACT_ANIMATION_DELAY_MS);
contractBackgroundColorAnimator.setDuration(CONTRACT_ANIMATION_MS);
@@ -152,8 +161,8 @@ class NumPadAnimator {
animator -> mBackground.setColor((int) animator.getAnimatedValue()));
ValueAnimator contractTextColorAnimator =
- ValueAnimator.ofObject(new ArgbEvaluator(), textColorPrimaryInverse,
- textColorPrimary);
+ ValueAnimator.ofObject(new ArgbEvaluator(), mTextColorPressed,
+ mTextColorPrimary);
contractTextColorAnimator.setInterpolator(Interpolators.LINEAR);
contractTextColorAnimator.setStartDelay(CONTRACT_ANIMATION_DELAY_MS);
contractTextColorAnimator.setDuration(CONTRACT_ANIMATION_MS);
@@ -161,6 +170,9 @@ class NumPadAnimator {
if (mDigitTextView != null) {
mDigitTextView.setTextColor((int) valueAnimator.getAnimatedValue());
}
+ if (mImageButton != null) {
+ mImageButton.setTint((int) valueAnimator.getAnimatedValue());
+ }
});
mContractAnimatorSet = new AnimatorSet();
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
index 7b98f2759d80..8099f75ed7d4 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
@@ -42,7 +42,7 @@ public class NumPadButton extends AlphaOptimizedImageButton {
Drawable background = getBackground();
if (background instanceof GradientDrawable) {
mAnimator = new NumPadAnimator(context, background.mutate(),
- attrs.getStyleAttribute());
+ attrs.getStyleAttribute(), getDrawable());
} else {
mAnimator = null;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
index f771c974fe60..4aed2513d4f2 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -134,7 +134,7 @@ public class NumPadKey extends ViewGroup {
Drawable background = getBackground();
if (background instanceof GradientDrawable) {
mAnimator = new NumPadAnimator(context, background.mutate(),
- R.style.NumPadKey, mDigitText);
+ R.style.NumPadKey, mDigitText, null);
} else {
mAnimator = null;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java b/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
index 6e0613036678..334bb1ec12cb 100644
--- a/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
@@ -76,12 +76,6 @@ public interface ViewMediatorCallback {
void playTrustedSound();
/**
- * When the bouncer is shown or hides
- * @param shown
- */
- void onBouncerVisiblityChanged(boolean shown);
-
- /**
* @return true if the screen is on
*/
boolean isScreenOn();
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 8d6509874776..dd312186afee 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -47,6 +47,7 @@ import android.hardware.graphics.common.AlphaInterpretation;
import android.hardware.graphics.common.DisplayDecorationSupport;
import android.os.Handler;
import android.os.SystemProperties;
+import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings.Secure;
import android.util.DisplayUtils;
@@ -899,8 +900,8 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
final DisplayInfo displayInfo = new DisplayInfo();
mContext.getDisplay().getDisplayInfo(displayInfo);
return DisplayUtils.getPhysicalPixelDisplaySizeRatio(
- stableDisplaySize.x, stableDisplaySize.y, displayInfo.logicalWidth,
- displayInfo.logicalHeight);
+ stableDisplaySize.x, stableDisplaySize.y, displayInfo.getNaturalWidth(),
+ displayInfo.getNaturalHeight());
}
@Override
@@ -1067,15 +1068,22 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
}
private void updateLayoutParams() {
- if (mOverlays == null) {
- return;
+ //ToDo: We should skip unnecessary call to update view layout.
+ Trace.beginSection("ScreenDecorations#updateLayoutParams");
+ if (mScreenDecorHwcWindow != null) {
+ mWindowManager.updateViewLayout(mScreenDecorHwcWindow, getHwcWindowLayoutParams());
}
- for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
- if (mOverlays[i] == null) {
- continue;
+
+ if (mOverlays != null) {
+ for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
+ if (mOverlays[i] == null) {
+ continue;
+ }
+ mWindowManager.updateViewLayout(
+ mOverlays[i].getRootView(), getWindowLayoutParams(i));
}
- mWindowManager.updateViewLayout(mOverlays[i].getRootView(), getWindowLayoutParams(i));
}
+ Trace.endSection();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index d59ad9286b10..75339aaa843d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -79,6 +79,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
import javax.inject.Inject;
@@ -278,6 +279,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
}
});
mUdfpsController.setAuthControllerUpdateUdfpsLocation(this::updateUdfpsLocation);
+ mUdfpsController.setHalControlsIllumination(mUdfpsProps.get(0).halControlsIllumination);
mUdfpsBounds = mUdfpsProps.get(0).getLocation().getRect();
updateUdfpsLocation();
}
@@ -445,11 +447,11 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
/**
* @return the radius of UDFPS on the screen in pixels
*/
- public int getUdfpsRadius() {
+ public float getUdfpsRadius() {
if (mUdfpsController == null || mUdfpsBounds == null) {
return -1;
}
- return mUdfpsBounds.height() / 2;
+ return mUdfpsBounds.height() / 2f;
}
/**
@@ -633,11 +635,17 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
displayInfo.getNaturalHeight());
final FingerprintSensorPropertiesInternal udfpsProp = mUdfpsProps.get(0);
+ final Rect previousUdfpsBounds = mUdfpsBounds;
mUdfpsBounds = udfpsProp.getLocation().getRect();
mUdfpsBounds.scale(scaleFactor);
mUdfpsController.updateOverlayParams(udfpsProp.sensorId,
new UdfpsOverlayParams(mUdfpsBounds, displayInfo.getNaturalWidth(),
displayInfo.getNaturalHeight(), scaleFactor, displayInfo.rotation));
+ if (!Objects.equals(previousUdfpsBounds, mUdfpsBounds)) {
+ for (Callback cb : mCallbacks) {
+ cb.onUdfpsLocationChanged();
+ }
+ }
}
}
@@ -1053,5 +1061,10 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
* Called when the biometric prompt is no longer showing.
*/
default void onBiometricPromptDismissed() {}
+
+ /**
+ * The location in pixels can change due to resolution changes.
+ */
+ default void onUdfpsLocationChanged() {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 76c1dbcaf20c..86e501670440 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -80,6 +80,7 @@ class AuthRippleController @Inject constructor(
@VisibleForTesting
internal var startLightRevealScrimOnKeyguardFadingAway = false
+ var lightRevealScrimAnimator: ValueAnimator? = null
var fingerprintSensorLocation: PointF? = null
private var faceSensorLocation: PointF? = null
private var circleReveal: LightRevealEffect? = null
@@ -163,7 +164,8 @@ class AuthRippleController @Inject constructor(
if (keyguardStateController.isKeyguardFadingAway) {
val lightRevealScrim = centralSurfaces.lightRevealScrim
if (startLightRevealScrimOnKeyguardFadingAway && lightRevealScrim != null) {
- ValueAnimator.ofFloat(.1f, 1f).apply {
+ lightRevealScrimAnimator?.cancel()
+ lightRevealScrimAnimator = ValueAnimator.ofFloat(.1f, 1f).apply {
interpolator = Interpolators.LINEAR_OUT_SLOW_IN
duration = RIPPLE_ANIMATION_DURATION
startDelay = keyguardStateController.keyguardFadingAwayDelay
@@ -183,6 +185,8 @@ class AuthRippleController @Inject constructor(
if (lightRevealScrim.revealEffect == circleReveal) {
lightRevealScrim.revealEffect = LiftReveal
}
+
+ lightRevealScrimAnimator = null
}
})
start()
@@ -192,6 +196,13 @@ class AuthRippleController @Inject constructor(
}
}
+ /**
+ * Whether we're animating the light reveal scrim from a call to [onKeyguardFadingAwayChanged].
+ */
+ fun isAnimatingLightRevealScrim(): Boolean {
+ return lightRevealScrimAnimator?.isRunning ?: false
+ }
+
override fun onStartedGoingToSleep() {
// reset the light reveal start in case we were pending an unlock
startLightRevealScrimOnKeyguardFadingAway = false
@@ -324,15 +335,17 @@ class AuthRippleController @Inject constructor(
updateSensorLocation()
}
- override fun onEnrollmentsChanged() {
+ override fun onUdfpsLocationChanged() {
+ updateUdfpsDependentParams()
+ updateSensorLocation()
}
}
private fun updateUdfpsDependentParams() {
authController.udfpsProps?.let {
if (it.size > 0) {
- udfpsRadius = it[0].location.sensorRadius.toFloat()
udfpsController = udfpsControllerProvider.get()
+ udfpsRadius = authController.udfpsRadius
if (mView.isAttachedToWindow) {
udfpsController?.addCallback(udfpsControllerCallback)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 432d2930453c..a35f0427e55a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -131,6 +131,7 @@ public class UdfpsController implements DozeReceiver {
// Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
// sensors, this, in addition to a lot of the code here, will be updated.
@VisibleForTesting int mSensorId;
+ private boolean mHalControlsIllumination;
@VisibleForTesting @NonNull UdfpsOverlayParams mOverlayParams = new UdfpsOverlayParams();
// TODO(b/229290039): UDFPS controller should manage its dimensions on its own. Remove this.
@Nullable private Runnable mAuthControllerUpdateUdfpsLocation;
@@ -201,9 +202,10 @@ public class UdfpsController implements DozeReceiver {
mKeyguardUpdateMonitor, mDialogManager, mDumpManager,
mLockscreenShadeTransitionController, mConfigurationController,
mSystemClock, mKeyguardStateController,
- mUnlockedScreenOffAnimationController, mHbmProvider, requestId, reason,
- callback, (view, event, fromUdfpsView) -> onTouch(requestId, event,
- fromUdfpsView), mActivityLaunchAnimator)));
+ mUnlockedScreenOffAnimationController, mHalControlsIllumination,
+ mHbmProvider, requestId, reason, callback,
+ (view, event, fromUdfpsView) -> onTouch(requestId, event,
+ fromUdfpsView), mActivityLaunchAnimator)));
}
@Override
@@ -310,6 +312,11 @@ public class UdfpsController implements DozeReceiver {
mAuthControllerUpdateUdfpsLocation = r;
}
+ // TODO(b/229290039): UDFPS controller should manage its properties on its own. Remove this.
+ public void setHalControlsIllumination(boolean value) {
+ mHalControlsIllumination = value;
+ }
+
/**
* Calculate the pointer speed given a velocity tracker and the pointer id.
* This assumes that the velocity tracker has already been passed all relevant motion events.
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 9c8aee4e93a7..2d51c973b0b1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -77,6 +77,7 @@ class UdfpsControllerOverlay(
private val systemClock: SystemClock,
private val keyguardStateController: KeyguardStateController,
private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
+ private val halControlsIllumination: Boolean,
private var hbmProvider: UdfpsHbmProvider,
val requestId: Long,
@ShowReason val requestReason: Int,
@@ -137,6 +138,7 @@ class UdfpsControllerOverlay(
R.layout.udfps_view, null, false
) as UdfpsView).apply {
overlayParams = params
+ halControlsIllumination = this@UdfpsControllerOverlay.halControlsIllumination
setHbmProvider(hbmProvider)
val animation = inflateUdfpsAnimation(this, controller)
if (animation != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
index 2f097924817e..8de721352069 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
@@ -154,7 +154,9 @@ public class UdfpsDialogMeasureAdapter {
//re-calculate the height of description
View description = mView.findViewById(R.id.description);
- totalHeight += measureDescription(description, displayHeight, width, totalHeight);
+ if (description != null && description.getVisibility() != View.GONE) {
+ totalHeight += measureDescription(description, displayHeight, width, totalHeight);
+ }
return new AuthDialog.LayoutParams(width, totalHeight);
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmProvider.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmProvider.java
index 38c937feb941..f26dd5f57061 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmProvider.java
@@ -34,8 +34,11 @@ public interface UdfpsHbmProvider {
* invoked from the UI thread.
*
* @param onHbmEnabled A runnable that will be executed once HBM is enabled.
+ *
+ * TODO(b/231335067): enableHbm with halControlsIllumination=true shouldn't make sense.
+ * This only makes sense now because vendor code may rely on the side effects of enableHbm.
*/
- void enableHbm(@Nullable Runnable onHbmEnabled);
+ void enableHbm(boolean halControlsIllumination, @Nullable Runnable onHbmEnabled);
/**
* UdfpsView will call this to disable HBM when illumination is no longer needed.
@@ -46,8 +49,6 @@ public interface UdfpsHbmProvider {
* The call must be made from the UI thread. The callback, if provided, will also be invoked
* from the UI thread.
*
- *
- *
* @param onHbmDisabled A runnable that will be executed once HBM is disabled.
*/
void disableHbm(@Nullable Runnable onHbmDisabled);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
index 2aa345ab28dc..245c2252d57b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
@@ -63,9 +63,12 @@ class UdfpsView(
/** View controller (can be different for enrollment, BiometricPrompt, Keyguard, etc.). */
var animationViewController: UdfpsAnimationViewController<*>? = null
- /** Parameters that affect the position and size of the overlay. Visible for testing. */
+ /** Parameters that affect the position and size of the overlay. */
var overlayParams = UdfpsOverlayParams()
+ /** Whether the HAL is responsible for enabling and disabling of LHBM. */
+ var halControlsIllumination: Boolean = true
+
/** Debug message. */
var debugMessage: String? = null
set(value) {
@@ -154,11 +157,17 @@ class UdfpsView(
}
private fun doIlluminate(onIlluminatedRunnable: Runnable?) {
- hbmProvider?.enableHbm() {
+ // TODO(b/231335067): enableHbm with halControlsIllumination=true shouldn't make sense.
+ // This only makes sense now because vendor code may rely on the side effects of enableHbm.
+ hbmProvider?.enableHbm(halControlsIllumination) {
if (onIlluminatedRunnable != null) {
- // No framework API can reliably tell when a frame reaches the panel. A timeout
- // is the safest solution.
- postDelayed(onIlluminatedRunnable, onIlluminatedDelayMs)
+ if (halControlsIllumination) {
+ onIlluminatedRunnable.run()
+ } else {
+ // No framework API can reliably tell when a frame reaches the panel. A timeout
+ // is the safest solution.
+ postDelayed(onIlluminatedRunnable, onIlluminatedDelayMs)
+ }
} else {
Log.w(TAG, "doIlluminate | onIlluminatedRunnable is null")
}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 726d00c230d7..ee8363fe50d4 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -38,6 +38,7 @@ import android.annotation.MainThread;
import android.app.RemoteAction;
import android.content.BroadcastReceiver;
import android.content.ClipData;
+import android.content.ClipDescription;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -128,6 +129,8 @@ public class ClipboardOverlayController {
private final View mClipboardPreview;
private final ImageView mImagePreview;
private final TextView mTextPreview;
+ private final TextView mHiddenTextPreview;
+ private final TextView mHiddenImagePreview;
private final View mPreviewBorder;
private final OverlayActionChip mEditChip;
private final OverlayActionChip mRemoteCopyChip;
@@ -186,6 +189,8 @@ public class ClipboardOverlayController {
mClipboardPreview = requireNonNull(mView.findViewById(R.id.clipboard_preview));
mImagePreview = requireNonNull(mView.findViewById(R.id.image_preview));
mTextPreview = requireNonNull(mView.findViewById(R.id.text_preview));
+ mHiddenTextPreview = requireNonNull(mView.findViewById(R.id.hidden_text_preview));
+ mHiddenImagePreview = requireNonNull(mView.findViewById(R.id.hidden_image_preview));
mPreviewBorder = requireNonNull(mView.findViewById(R.id.preview_border));
mEditChip = requireNonNull(mView.findViewById(R.id.edit_chip));
mRemoteCopyChip = requireNonNull(mView.findViewById(R.id.remote_copy_chip));
@@ -270,21 +275,32 @@ public class ClipboardOverlayController {
mExitAnimator.cancel();
}
reset();
+
+ boolean isSensitive = clipData != null && clipData.getDescription().getExtras() != null
+ && clipData.getDescription().getExtras()
+ .getBoolean(ClipDescription.EXTRA_IS_SENSITIVE);
if (clipData == null || clipData.getItemCount() == 0) {
- showTextPreview(mContext.getResources().getString(
- R.string.clipboard_overlay_text_copied));
+ showTextPreview(
+ mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
+ mTextPreview);
} else if (!TextUtils.isEmpty(clipData.getItemAt(0).getText())) {
ClipData.Item item = clipData.getItemAt(0);
if (item.getTextLinks() != null) {
AsyncTask.execute(() -> classifyText(clipData.getItemAt(0), clipSource));
}
- showEditableText(item.getText());
+ if (isSensitive) {
+ showEditableText(
+ mContext.getResources().getString(R.string.clipboard_text_hidden), true);
+ } else {
+ showEditableText(item.getText(), false);
+ }
} else if (clipData.getItemAt(0).getUri() != null) {
// How to handle non-image URIs?
- showEditableImage(clipData.getItemAt(0).getUri());
+ showEditableImage(clipData.getItemAt(0).getUri(), isSensitive);
} else {
showTextPreview(
- mContext.getResources().getString(R.string.clipboard_overlay_text_copied));
+ mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
+ mTextPreview);
}
Intent remoteCopyIntent = getRemoteCopyIntent(clipData);
// Only show remote copy if it's available.
@@ -406,15 +422,23 @@ public class ClipboardOverlayController {
animateOut();
}
- private void showTextPreview(CharSequence text) {
- mTextPreview.setVisibility(View.VISIBLE);
+ private void showSinglePreview(View v) {
+ mTextPreview.setVisibility(View.GONE);
mImagePreview.setVisibility(View.GONE);
- mTextPreview.setText(text.subSequence(0, Math.min(500, text.length())));
+ mHiddenTextPreview.setVisibility(View.GONE);
+ mHiddenImagePreview.setVisibility(View.GONE);
+ v.setVisibility(View.VISIBLE);
+ }
+
+ private void showTextPreview(CharSequence text, TextView textView) {
+ showSinglePreview(textView);
+ textView.setText(text.subSequence(0, Math.min(500, text.length())));
mEditChip.setVisibility(View.GONE);
}
- private void showEditableText(CharSequence text) {
- showTextPreview(text);
+ private void showEditableText(CharSequence text, boolean hidden) {
+ TextView textView = hidden ? mHiddenTextPreview : mTextPreview;
+ showTextPreview(text, textView);
mEditChip.setVisibility(View.VISIBLE);
mActionContainerBackground.setVisibility(View.VISIBLE);
mEditChip.setAlpha(1f);
@@ -422,32 +446,36 @@ public class ClipboardOverlayController {
mContext.getString(R.string.clipboard_edit_text_description));
View.OnClickListener listener = v -> editText();
mEditChip.setOnClickListener(listener);
- mTextPreview.setOnClickListener(listener);
+ textView.setOnClickListener(listener);
}
- private void showEditableImage(Uri uri) {
- ContentResolver resolver = mContext.getContentResolver();
- try {
- int size = mContext.getResources().getDimensionPixelSize(R.dimen.overlay_x_scale);
- // The width of the view is capped, height maintains aspect ratio, so allow it to be
- // taller if needed.
- Bitmap thumbnail = resolver.loadThumbnail(uri, new Size(size, size * 4), null);
- mImagePreview.setImageBitmap(thumbnail);
- } catch (IOException e) {
- Log.e(TAG, "Thumbnail loading failed", e);
- showTextPreview(
- mContext.getResources().getString(R.string.clipboard_overlay_text_copied));
- return;
- }
- mTextPreview.setVisibility(View.GONE);
- mImagePreview.setVisibility(View.VISIBLE);
+ private void showEditableImage(Uri uri, boolean isSensitive) {
mEditChip.setAlpha(1f);
mActionContainerBackground.setVisibility(View.VISIBLE);
View.OnClickListener listener = v -> editImage(uri);
+ if (isSensitive) {
+ showSinglePreview(mHiddenImagePreview);
+ mHiddenImagePreview.setOnClickListener(listener);
+ } else {
+ showSinglePreview(mImagePreview);
+ ContentResolver resolver = mContext.getContentResolver();
+ try {
+ int size = mContext.getResources().getDimensionPixelSize(R.dimen.overlay_x_scale);
+ // The width of the view is capped, height maintains aspect ratio, so allow it to be
+ // taller if needed.
+ Bitmap thumbnail = resolver.loadThumbnail(uri, new Size(size, size * 4), null);
+ mImagePreview.setImageBitmap(thumbnail);
+ } catch (IOException e) {
+ Log.e(TAG, "Thumbnail loading failed", e);
+ showTextPreview(
+ mContext.getResources().getString(R.string.clipboard_overlay_text_copied),
+ mTextPreview);
+ }
+ mImagePreview.setOnClickListener(listener);
+ }
mEditChip.setOnClickListener(listener);
mEditChip.setContentDescription(
mContext.getString(R.string.clipboard_edit_image_description));
- mImagePreview.setOnClickListener(listener);
}
private Intent getRemoteCopyIntent(ClipData clipData) {
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
index 0d8987988f0a..b54b83274f45 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
@@ -20,10 +20,12 @@ import static java.util.Objects.requireNonNull;
import android.app.Activity;
import android.content.ClipData;
+import android.content.ClipDescription;
import android.content.ClipboardManager;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
+import android.os.PersistableBundle;
import android.util.Log;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
@@ -41,6 +43,7 @@ public class EditTextActivity extends Activity
private EditText mEditText;
private ClipboardManager mClipboardManager;
private TextView mAttribution;
+ private boolean mSensitive;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -72,6 +75,9 @@ public class EditTextActivity extends Activity
}
mEditText.setText(clip.getItemAt(0).getText());
mEditText.requestFocus();
+ mSensitive = clip.getDescription().getExtras() != null
+ && clip.getDescription().getExtras()
+ .getBoolean(ClipDescription.EXTRA_IS_SENSITIVE);
mClipboardManager.addPrimaryClipChangedListener(this);
}
@@ -88,6 +94,9 @@ public class EditTextActivity extends Activity
private void saveToClipboard() {
ClipData clip = ClipData.newPlainText("text", mEditText.getText());
+ PersistableBundle extras = new PersistableBundle();
+ extras.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, mSensitive);
+ clip.getDescription().setExtras(extras);
mClipboardManager.setPrimaryClip(clip);
hideImeAndFinish();
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
index f9115b20ca06..3eb58bba1ca4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
@@ -343,7 +343,7 @@ private class ControlHolderAccessibilityDelegate(
info.className = Switch::class.java.name
}
- override fun performAccessibilityAction(host: View?, action: Int, args: Bundle?): Boolean {
+ override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {
if (super.performAccessibilityAction(host, action, args)) {
return true
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 5d154c3b4f6b..4e48a5261f6b 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -38,7 +38,6 @@ import com.android.systemui.media.dagger.MediaModule;
import com.android.systemui.plugins.qs.QSFactory;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.EnhancedEstimates;
-import com.android.systemui.power.EnhancedEstimatesImpl;
import com.android.systemui.power.dagger.PowerModule;
import com.android.systemui.qs.dagger.QSModule;
import com.android.systemui.qs.tileimpl.QSFactoryImpl;
@@ -80,8 +79,19 @@ import dagger.Module;
import dagger.Provides;
/**
- * A dagger module for injecting default implementations of components of System UI that may be
- * overridden by the System UI implementation.
+ * A dagger module for injecting default implementations of components of System UI.
+ *
+ * Variants of SystemUI should make a copy of this, include it in their component, and customize it
+ * as needed.
+ *
+ * This module might alternatively be named `AospSystemUIModule`, `PhoneSystemUIModule`,
+ * or `BasicSystemUIModule`.
+ *
+ * Nothing in the module should be strictly required. Each piece should either be swappable with
+ * a different implementation or entirely removable.
+ *
+ * This is different from {@link SystemUIModule} which should be used for pieces of required
+ * SystemUI code that variants of SystemUI _must_ include to function correctly.
*/
@Module(includes = {
MediaModule.class,
@@ -90,7 +100,7 @@ import dagger.Provides;
StartCentralSurfacesModule.class,
VolumeModule.class
})
-public abstract class SystemUIDefaultModule {
+public abstract class ReferenceSystemUIModule {
@SysUISingleton
@Provides
@@ -101,9 +111,6 @@ public abstract class SystemUIDefaultModule {
}
@Binds
- abstract EnhancedEstimates bindEnhancedEstimates(EnhancedEstimatesImpl enhancedEstimates);
-
- @Binds
abstract NotificationLockscreenUserManager bindNotificationLockscreenUserManager(
NotificationLockscreenUserManagerImpl notificationLockscreenUserManager);
@@ -148,6 +155,7 @@ public abstract class SystemUIDefaultModule {
return spC;
}
+ /** */
@Binds
@SysUISingleton
public abstract QSFactory bindQSFactory(QSFactoryImpl qsFactoryImpl);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 701972ab7264..5d34a6987b66 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -73,7 +73,7 @@ import dagger.Subcomponent;
SystemUIBinder.class,
SystemUIModule.class,
SystemUICoreStartableModule.class,
- SystemUIDefaultModule.class})
+ ReferenceSystemUIModule.class})
public interface SysUIComponent {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index bbeb66c5af52..535eff801878 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -100,8 +100,14 @@ import dagger.Module;
import dagger.Provides;
/**
- * A dagger module for injecting components of System UI that are not overridden by the System UI
- * implementation.
+ * A dagger module for injecting components of System UI that are required by System UI.
+ *
+ * If your feature can be excluded, subclassed, or re-implemented by a variant of SystemUI, put
+ * your Dagger Module in {@link ReferenceSystemUIModule} and/or any variant modules that
+ * rely on the feature.
+ *
+ * Adding an entry in this file means that _all_ variants of SystemUI will receive that code. They
+ * may not appreciate that.
*/
@Module(includes = {
AppOpsModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 74044e2c2eb8..1cc5df5d04cf 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -39,7 +39,6 @@ import com.android.systemui.dock.DockManager;
import com.android.systemui.doze.DozeMachine.State;
import com.android.systemui.doze.dagger.DozeScope;
import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.Assert;
@@ -94,7 +93,6 @@ public class DozeTriggers implements DozeMachine.Part {
private final AuthController mAuthController;
private final DelayableExecutor mMainExecutor;
private final KeyguardStateController mKeyguardStateController;
- private final BatteryController mBatteryController;
private final UiEventLogger mUiEventLogger;
private final DevicePostureController mDevicePostureController;
@@ -186,8 +184,7 @@ public class DozeTriggers implements DozeMachine.Part {
@Main DelayableExecutor mainExecutor,
UiEventLogger uiEventLogger,
KeyguardStateController keyguardStateController,
- DevicePostureController devicePostureController,
- BatteryController batteryController) {
+ DevicePostureController devicePostureController) {
mContext = context;
mDozeHost = dozeHost;
mConfig = config;
@@ -208,7 +205,6 @@ public class DozeTriggers implements DozeMachine.Part {
mMainExecutor = mainExecutor;
mUiEventLogger = uiEventLogger;
mKeyguardStateController = keyguardStateController;
- mBatteryController = batteryController;
}
private final DevicePostureController.Callback mDevicePostureCallback =
posture -> {
@@ -320,12 +316,7 @@ public class DozeTriggers implements DozeMachine.Part {
gentleWakeUp(pulseReason);
} else if (isPickup) {
if (shouldDropPickupEvent()) {
- mDozeLog.traceSensorEventDropped(
- pulseReason,
- "keyguardOccluded="
- + mKeyguardStateController.isOccluded()
- + " pluggedInWireless="
- + mBatteryController.isPluggedInWireless());
+ mDozeLog.traceSensorEventDropped(pulseReason, "keyguard occluded");
return;
}
gentleWakeUp(pulseReason);
@@ -356,7 +347,7 @@ public class DozeTriggers implements DozeMachine.Part {
}
private boolean shouldDropPickupEvent() {
- return mKeyguardStateController.isOccluded() || mBatteryController.isPluggedInWireless();
+ return mKeyguardStateController.isOccluded();
}
private void gentleWakeUp(int reason) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index fb09132684eb..14a7e3c7f013 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -44,6 +44,7 @@ import com.android.systemui.shared.system.QuickStepContract
import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController
import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController
import com.android.systemui.shared.system.smartspace.SmartspaceState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.policy.KeyguardStateController
import dagger.Lazy
@@ -140,7 +141,8 @@ class KeyguardUnlockAnimationController @Inject constructor(
keyguardViewMediator: Lazy<KeyguardViewMediator>,
private val keyguardViewController: KeyguardViewController,
private val featureFlags: FeatureFlags,
- private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>
+ private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>,
+ private val statusBarStateController: SysuiStatusBarStateController
) : KeyguardStateController.Callback, ISysuiUnlockAnimationController.Stub() {
interface KeyguardUnlockAnimationListener {
@@ -372,7 +374,8 @@ class KeyguardUnlockAnimationController @Inject constructor(
* changed.
*/
override fun onKeyguardGoingAwayChanged() {
- if (keyguardStateController.isKeyguardGoingAway) {
+ if (keyguardStateController.isKeyguardGoingAway
+ && !statusBarStateController.leaveOpenOnKeyguardHide()) {
prepareForInWindowLauncherAnimations()
}
}
@@ -813,6 +816,11 @@ class KeyguardUnlockAnimationController @Inject constructor(
return false
}
+ // The smartspace is not visible if the bouncer is showing, so don't shared element it.
+ if (keyguardStateController.isBouncerShowing) {
+ return false
+ }
+
// We started to swipe to dismiss, but now we're doing a fling animation to complete the
// dismiss. In this case, the smartspace swiped away with the rest of the keyguard, so don't
// do the shared element transition.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 10ea1e06c6d7..4d59f1a8e363 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -779,16 +779,6 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
}
@Override
- public void onBouncerVisiblityChanged(boolean shown) {
- synchronized (KeyguardViewMediator.this) {
- if (shown) {
- mPendingPinLock = false;
- }
- adjustStatusBarLocked(shown, false);
- }
- }
-
- @Override
public void playTrustedSound() {
KeyguardViewMediator.this.playTrustedSound();
}
@@ -921,12 +911,12 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
RemoteAnimationTarget[] wallpapers,
RemoteAnimationTarget[] nonApps,
IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
+ setOccluded(false /* isOccluded */, true /* animate */);
+
if (apps == null || apps.length == 0 || apps[0] == null) {
Log.d(TAG, "No apps provided to unocclude runner; "
+ "skipping animation and unoccluding.");
-
finishedCallback.onAnimationFinished();
- setOccluded(false /* isOccluded */, true /* animate */);
return;
}
@@ -971,7 +961,6 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
@Override
public void onAnimationEnd(Animator animation) {
try {
- setOccluded(false /* isOccluded */, true /* animate */);
finishedCallback.onAnimationFinished();
mUnoccludeAnimator = null;
} catch (RemoteException e) {
@@ -989,6 +978,19 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
private DozeParameters mDozeParameters;
private final KeyguardStateController mKeyguardStateController;
+ private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
+ new KeyguardStateController.Callback() {
+ @Override
+ public void onBouncerShowingChanged() {
+ synchronized (KeyguardViewMediator.this) {
+ if (mKeyguardStateController.isBouncerShowing()) {
+ mPendingPinLock = false;
+ }
+ adjustStatusBarLocked(mKeyguardStateController.isBouncerShowing(), false);
+ }
+ }
+ };
+
private final Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy;
private final InteractionJankMonitor mInteractionJankMonitor;
private boolean mWallpaperSupportsAmbientMode;
@@ -1059,6 +1061,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
statusBarStateController.addCallback(this);
mKeyguardStateController = keyguardStateController;
+ keyguardStateController.addCallback(mKeyguardStateControllerCallback);
mKeyguardUnlockAnimationControllerLazy = keyguardUnlockAnimationControllerLazy;
mScreenOffAnimationController = screenOffAnimationController;
mInteractionJankMonitor = interactionJankMonitor;
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 2fec49955c2d..d65940172b17 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -636,6 +636,10 @@ public class MediaControlPanel {
mIsArtworkBound = isArtworkBound;
}
+ // Scrim bounds are set manually so it scales as expected
+ albumView.getForeground().setBounds(0, 0,
+ Math.max(width, height), Math.max(width, height));
+
// Transition Colors to current color scheme
mColorSchemeTransition.updateColorScheme(colorScheme, mIsArtworkBound);
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
index 1437c965512e..7eccb3b91bb5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
@@ -278,13 +278,14 @@ class MediaViewController @Inject constructor(
/**
* Apply squishFraction to a copy of viewState such that the cached version is untouched.
*/
- private fun squishViewState(
+ @VisibleForTesting
+ internal fun squishViewState(
viewState: TransitionViewState,
squishFraction: Float
): TransitionViewState {
val squishedViewState = viewState.copy()
squishedViewState.height = (squishedViewState.height * squishFraction).toInt()
- val albumArtViewState = viewState.widgetStates.get(R.id.album_art)
+ val albumArtViewState = squishedViewState.widgetStates.get(R.id.album_art)
if (albumArtViewState != null) {
albumArtViewState.height = squishedViewState.height
}
@@ -317,6 +318,7 @@ class MediaViewController @Inject constructor(
if (transitionLayout == null) {
return null
}
+
// Not cached. Let's create a new measurement
if (state.expansion == 0.0f || state.expansion == 1.0f) {
result = transitionLayout!!.calculateViewState(
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index 5bb6557c3fe7..8dcca3d55c28 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -449,7 +449,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
abstract int getStopButtonVisibility();
public CharSequence getStopButtonText() {
- return mContext.getText(R.string.keyboard_key_media_stop);
+ return mContext.getText(R.string.media_output_dialog_button_stop_casting);
}
public void onStopButtonClick() {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index e7f97d25fabe..58b6ad3e51e8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -55,8 +55,6 @@ import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.core.graphics.drawable.IconCompat;
-import androidx.mediarouter.media.MediaRouter;
-import androidx.mediarouter.media.MediaRouterParams;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.Utils;
@@ -215,9 +213,8 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
}
boolean shouldShowLaunchSection() {
- MediaRouterParams routerParams = MediaRouter.getInstance(mContext).getRouterParams();
- Log.d(TAG, "try to get routerParams: " + routerParams);
- return routerParams != null && !routerParams.isMediaTransferReceiverEnabled();
+ // TODO(b/231398073): Implements this when available.
+ return false;
}
void setRefreshing(boolean refreshing) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
index 9248433a4a90..026a3055b01b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
@@ -104,7 +104,7 @@ public class MediaOutputDialog extends MediaOutputBaseDialog {
@Override
public CharSequence getStopButtonText() {
- int resId = R.string.keyboard_key_media_stop;
+ int resId = R.string.media_output_dialog_button_stop_casting;
if (isBroadcastSupported() && mMediaOutputController.isPlaying()
&& !mMediaOutputController.isBluetoothLeBroadcastEnabled()) {
resId = R.string.media_output_broadcast;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index c7cd48fd5adf..3c373f447c34 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -70,6 +70,7 @@ import android.graphics.Insets;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.graphics.Region;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -91,6 +92,8 @@ import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.InternalInsetsInfo;
+import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
import android.view.WindowManager;
@@ -121,10 +124,12 @@ import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
import com.android.systemui.navigationbar.buttons.DeadZone;
import com.android.systemui.navigationbar.buttons.KeyButtonView;
import com.android.systemui.navigationbar.buttons.RotationContextButton;
+import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
import com.android.systemui.navigationbar.gestural.QuickswitchOrientedNavHandle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.recents.Recents;
+import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.shared.rotation.RotationButton;
import com.android.systemui.shared.rotation.RotationButtonController;
@@ -152,6 +157,7 @@ import com.android.wm.shell.pip.Pip;
import java.io.PrintWriter;
import java.util.Locale;
+import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
@@ -198,11 +204,14 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
private final Optional<Recents> mRecentsOptional;
private final DeviceConfigProxy mDeviceConfigProxy;
private final NavigationBarTransitions mNavigationBarTransitions;
+ private final EdgeBackGestureHandler mEdgeBackGestureHandler;
private final Optional<BackAnimation> mBackAnimation;
private final Handler mHandler;
private final UiEventLogger mUiEventLogger;
private final NavBarHelper mNavBarHelper;
private final NotificationShadeDepthController mNotificationShadeDepthController;
+ private final UserContextProvider mUserContextProvider;
+ private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener;
private NavigationBarFrame mFrame;
private @WindowVisibleState int mNavigationBarWindowState = WINDOW_STATE_SHOWING;
@@ -514,7 +523,9 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
DeadZone deadZone,
DeviceConfigProxy deviceConfigProxy,
NavigationBarTransitions navigationBarTransitions,
- Optional<BackAnimation> backAnimation) {
+ EdgeBackGestureHandler edgeBackGestureHandler,
+ Optional<BackAnimation> backAnimation,
+ UserContextProvider userContextProvider) {
super(navigationBarView);
mFrame = navigationBarFrame;
mContext = context;
@@ -539,6 +550,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
mDeadZone = deadZone;
mDeviceConfigProxy = deviceConfigProxy;
mNavigationBarTransitions = navigationBarTransitions;
+ mEdgeBackGestureHandler = edgeBackGestureHandler;
mBackAnimation = backAnimation;
mHandler = mainHandler;
mUiEventLogger = uiEventLogger;
@@ -550,7 +562,31 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
mAutoHideControllerFactory = autoHideControllerFactory;
mTelecomManagerOptional = telecomManagerOptional;
mInputMethodManager = inputMethodManager;
+ mUserContextProvider = userContextProvider;
+
+ mOnComputeInternalInsetsListener = info -> {
+ // When the nav bar is in 2-button or 3-button mode, or when IME is visible in fully
+ // gestural mode, the entire nav bar should be touchable.
+ if (!mEdgeBackGestureHandler.isHandlingGestures()) {
+ // We're in 2/3 button mode OR back button force-shown in SUW
+ if (!mImeVisible) {
+ // IME not showing, take all touches
+ info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
+ }
+ if (!mView.isImeRenderingNavButtons()) {
+ // IME showing but not drawing any buttons, take all touches
+ info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
+ }
+ }
+ // When in gestural and the IME is showing, don't use the nearest region since it will
+ // take gesture space away from the IME
+ info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+ info.touchableRegion.set(getButtonLocations(false /* includeFloatingButtons */,
+ false /* inScreen */, false /* useNearestRegion */));
+ };
+
+ mView.setEdgeBackGestureHandler(mEdgeBackGestureHandler);
mNavBarMode = mNavigationModeController.addListener(mModeChangedListener);
}
@@ -565,6 +601,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
mView.setBarTransitions(mNavigationBarTransitions);
mView.setTouchHandler(mTouchHandler);
mView.setNavBarMode(mNavBarMode);
+ mEdgeBackGestureHandler.setStateChangeCallback(mView::updateStates);
mView.updateRotationButton();
mView.setVisibility(
@@ -642,11 +679,14 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
mView.setNavBarMode(mNavBarMode);
mView.setUpdateActiveTouchRegionsCallback(
() -> mOverviewProxyService.onActiveNavBarRegionChanges(
- mView.getButtonLocations(
+ getButtonLocations(
true /* includeFloatingButtons */,
true /* inScreen */,
true /* useNearestRegion */)));
+ mView.getViewTreeObserver().addOnComputeInternalInsetsListener(
+ mOnComputeInternalInsetsListener);
+
mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
mPipOptional.ifPresent(mView::addPipExclusionBoundsChangeListener);
@@ -717,6 +757,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
mOrientationHandle.getViewTreeObserver().removeOnGlobalLayoutListener(
mOrientationHandleGlobalLayoutListener);
}
+ mView.getViewTreeObserver().removeOnComputeInternalInsetsListener(
+ mOnComputeInternalInsetsListener);
mHandler.removeCallbacks(mAutoDim);
mHandler.removeCallbacks(mOnVariableDurationHomeLongClick);
mHandler.removeCallbacks(mEnableLayoutTransitions);
@@ -1040,8 +1082,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
}
private void handleTransientChanged() {
- mView.onTransientStateChanged(mTransientShown,
- mTransientShownFromGestureOnSystemBar);
+ mEdgeBackGestureHandler.onNavBarTransientStateChanged(mTransientShown);
+
final int transitionMode = transitionMode(mTransientShown, mAppearance);
if (updateTransitionMode(transitionMode) && mLightBarController != null) {
mLightBarController.onNavigationBarModeChanged(transitionMode);
@@ -1512,35 +1554,36 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
int insetsHeight = -1;
int gravity = Gravity.BOTTOM;
boolean navBarCanMove = true;
+ final Context userContext = mUserContextProvider.createCurrentUserContext(mContext);
if (mWindowManager != null && mWindowManager.getCurrentWindowMetrics() != null) {
Rect displaySize = mWindowManager.getCurrentWindowMetrics().getBounds();
navBarCanMove = displaySize.width() != displaySize.height()
- && mContext.getResources().getBoolean(
+ && userContext.getResources().getBoolean(
com.android.internal.R.bool.config_navBarCanMove);
}
if (!navBarCanMove) {
- height = mContext.getResources().getDimensionPixelSize(
+ height = userContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_frame_height);
- insetsHeight = mContext.getResources().getDimensionPixelSize(
+ insetsHeight = userContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_height);
} else {
switch (rotation) {
case ROTATION_UNDEFINED:
case Surface.ROTATION_0:
case Surface.ROTATION_180:
- height = mContext.getResources().getDimensionPixelSize(
+ height = userContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_frame_height);
- insetsHeight = mContext.getResources().getDimensionPixelSize(
+ insetsHeight = userContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_height);
break;
case Surface.ROTATION_90:
gravity = Gravity.RIGHT;
- width = mContext.getResources().getDimensionPixelSize(
+ width = userContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_width);
break;
case Surface.ROTATION_270:
gravity = Gravity.LEFT;
- width = mContext.getResources().getDimensionPixelSize(
+ width = userContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_width);
break;
}
@@ -1565,12 +1608,12 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
lp.providedInternalInsets[ITYPE_NAVIGATION_BAR] = null;
}
lp.token = new Binder();
- lp.accessibilityTitle = mContext.getString(R.string.nav_bar);
+ lp.accessibilityTitle = userContext.getString(R.string.nav_bar);
lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC
| WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
lp.windowAnimations = 0;
- lp.setTitle("NavigationBar" + mContext.getDisplayId());
+ lp.setTitle("NavigationBar" + userContext.getDisplayId());
lp.setFitInsetsTypes(0 /* types */);
lp.setTrustedOverlay();
return lp;
@@ -1634,6 +1677,79 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
mNavigationIconHints = hints;
}
+ /**
+ * @param includeFloatingButtons Whether to include the floating rotation and overlay button in
+ * the region for all the buttons
+ * @param inScreenSpace Whether to return values in screen space or window space
+ * @param useNearestRegion Whether to use the nearest region instead of the actual button bounds
+ * @return
+ */
+ Region getButtonLocations(boolean includeFloatingButtons, boolean inScreenSpace,
+ boolean useNearestRegion) {
+ if (useNearestRegion && !inScreenSpace) {
+ // We currently don't support getting the nearest region in anything but screen space
+ useNearestRegion = false;
+ }
+ Region region = new Region();
+ Map<View, Rect> touchRegionCache = mView.getButtonTouchRegionCache();
+ updateButtonLocation(
+ region, touchRegionCache, mView.getBackButton(), inScreenSpace, useNearestRegion);
+ updateButtonLocation(
+ region, touchRegionCache, mView.getHomeButton(), inScreenSpace, useNearestRegion);
+ updateButtonLocation(region, touchRegionCache, mView.getRecentsButton(), inScreenSpace,
+ useNearestRegion);
+ updateButtonLocation(region, touchRegionCache, mView.getImeSwitchButton(), inScreenSpace,
+ useNearestRegion);
+ updateButtonLocation(
+ region, touchRegionCache, mView.getAccessibilityButton(), inScreenSpace,
+ useNearestRegion);
+ if (includeFloatingButtons && mView.getFloatingRotationButton().isVisible()) {
+ // Note: this button is floating so the nearest region doesn't apply
+ updateButtonLocation(
+ region, mView.getFloatingRotationButton().getCurrentView(), inScreenSpace);
+ } else {
+ updateButtonLocation(region, touchRegionCache, mView.getRotateSuggestionButton(),
+ inScreenSpace, useNearestRegion);
+ }
+ return region;
+ }
+
+ private void updateButtonLocation(
+ Region region,
+ Map<View, Rect> touchRegionCache,
+ ButtonDispatcher button,
+ boolean inScreenSpace,
+ boolean useNearestRegion) {
+ if (button == null) {
+ return;
+ }
+ View view = button.getCurrentView();
+ if (view == null || !button.isVisible()) {
+ return;
+ }
+ // If the button is tappable from perspective of NearestTouchFrame, then we'll
+ // include the regions where the tap is valid instead of just the button layout location
+ if (useNearestRegion && touchRegionCache.containsKey(view)) {
+ region.op(touchRegionCache.get(view), Region.Op.UNION);
+ return;
+ }
+ updateButtonLocation(region, view, inScreenSpace);
+ }
+
+ private void updateButtonLocation(Region region, View view, boolean inScreenSpace) {
+ Rect bounds = new Rect();
+ if (inScreenSpace) {
+ view.getBoundsOnScreen(bounds);
+ } else {
+ int[] location = new int[2];
+ view.getLocationInWindow(location);
+ bounds.set(location[0], location[1],
+ location[0] + view.getWidth(),
+ location[1] + view.getHeight());
+ }
+ region.op(bounds, Region.Op.UNION);
+ }
+
private final ModeChangedListener mModeChangedListener = new ModeChangedListener() {
@Override
public void onNavigationModeChanged(int mode) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java
index 93bf136e88f1..f6bfd6c8f9c5 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java
@@ -24,6 +24,7 @@ import android.view.WindowManager;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.navigationbar.NavigationBarComponent.NavigationBarScope;
+import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
import dagger.Module;
import dagger.Provides;
@@ -55,6 +56,14 @@ public interface NavigationBarModule {
return barView.findViewById(R.id.navigation_bar_view);
}
+ /** */
+ @Provides
+ @NavigationBarScope
+ static EdgeBackGestureHandler provideEdgeBackGestureHandler(
+ EdgeBackGestureHandler.Factory factory, @DisplayId Context context) {
+ return factory.create(context);
+ }
+
/** A WindowManager specific to the display's context. */
@Provides
@NavigationBarScope
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 6e00ebc1dc5d..a13c199df41e 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -41,8 +41,6 @@ import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.Rect;
-import android.graphics.Region;
-import android.graphics.Region.Op;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
@@ -53,8 +51,6 @@ import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewTreeObserver.InternalInsetsInfo;
-import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
import android.view.WindowInsets;
import android.view.WindowInsetsController.Behavior;
import android.view.WindowManager;
@@ -93,7 +89,6 @@ import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.pip.Pip;
import java.io.PrintWriter;
-import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Executor;
@@ -123,11 +118,6 @@ public class NavigationBarView extends FrameLayout {
private int mNavBarMode;
private boolean mImeDrawsImeNavBar;
- private final Region mTmpRegion = new Region();
- private final int[] mTmpPosition = new int[2];
- private Rect mTmpBounds = new Rect();
- private Map<View, Rect> mButtonFullTouchableRegions = new HashMap<>();
-
private KeyButtonDrawable mBackIcon;
private KeyButtonDrawable mHomeDefaultIcon;
private KeyButtonDrawable mRecentIcon;
@@ -138,7 +128,6 @@ public class NavigationBarView extends FrameLayout {
private EdgeBackGestureHandler mEdgeBackGestureHandler;
private final DeadZone mDeadZone;
- private boolean mDeadZoneConsuming = false;
private NavigationBarTransitions mBarTransitions;
@Nullable
private AutoHideController mAutoHideController;
@@ -152,7 +141,6 @@ public class NavigationBarView extends FrameLayout {
private boolean mUseCarModeUi = false;
private boolean mInCarMode = false;
private boolean mDockedStackExists;
- private boolean mImeVisible;
private boolean mScreenOn = true;
private final SparseArray<ButtonDispatcher> mButtonDispatchers = new SparseArray<>();
@@ -272,31 +260,6 @@ public class NavigationBarView extends FrameLayout {
}
};
- private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener = info -> {
- // When the nav bar is in 2-button or 3-button mode, or when the back button is force-shown
- // while in gesture nav in SUW, the entire nav bar should be touchable.
- if (!mEdgeBackGestureHandler.isHandlingGestures()) {
- // We're in 2/3 button mode OR back button force-shown in SUW
- if (!mImeVisible) {
- // IME not showing, take all touches
- info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
- return;
- }
-
- if (!isImeRenderingNavButtons()) {
- // IME showing but not drawing any buttons, take all touches
- info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
- return;
- }
- }
-
- // When in gestural and the IME is showing, don't use the nearest region since it will take
- // gesture space away from the IME
- info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
- info.touchableRegion.set(getButtonLocations(false /* includeFloatingButtons */,
- false /* inScreen */, false /* useNearestRegion */));
- };
-
private final RotationButtonUpdatesCallback mRotationButtonListener =
new RotationButtonUpdatesCallback() {
@Override
@@ -315,13 +278,6 @@ public class NavigationBarView extends FrameLayout {
}
};
- private final Consumer<Boolean> mNavbarOverlayVisibilityChangeCallback = (visible) -> {
- if (visible && mAutoHideController != null) {
- mAutoHideController.touchAutoHide();
- }
- notifyActiveTouchRegions();
- };
-
public NavigationBarView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -380,9 +336,6 @@ public class NavigationBarView extends FrameLayout {
mNavColorSampleMargin = getResources()
.getDimensionPixelSize(R.dimen.navigation_handle_sample_horizontal_margin);
- mEdgeBackGestureHandler = Dependency.get(EdgeBackGestureHandler.Factory.class)
- .create(mContext);
- mEdgeBackGestureHandler.setStateChangeCallback(this::updateStates);
Executor backgroundExecutor = Dependency.get(Dependency.BACKGROUND_EXECUTOR);
mRegionSamplingHelper = new RegionSamplingHelper(this,
new RegionSamplingHelper.SamplingCallback() {
@@ -409,6 +362,10 @@ public class NavigationBarView extends FrameLayout {
}, backgroundExecutor);
}
+ public void setEdgeBackGestureHandler(EdgeBackGestureHandler edgeBackGestureHandler) {
+ mEdgeBackGestureHandler = edgeBackGestureHandler;
+ }
+
void setBarTransitions(NavigationBarTransitions navigationBarTransitions) {
mBarTransitions = navigationBarTransitions;
}
@@ -681,8 +638,7 @@ public class NavigationBarView extends FrameLayout {
if (!visible) {
mTransitionListener.onBackAltCleared();
}
- mImeVisible = visible;
- mRotationButtonController.getRotationButton().setCanShowRotationButton(!mImeVisible);
+ mRotationButtonController.getRotationButton().setCanShowRotationButton(!visible);
}
void setDisabledFlags(int disabledFlags, SysUiState sysUiState) {
@@ -774,7 +730,7 @@ public class NavigationBarView extends FrameLayout {
/**
* Returns whether the IME is currently visible and drawing the nav buttons.
*/
- private boolean isImeRenderingNavButtons() {
+ boolean isImeRenderingNavButtons() {
return mImeDrawsImeNavBar
&& mImeCanRenderGesturalNavButtons
&& (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0;
@@ -1003,75 +959,14 @@ public class NavigationBarView extends FrameLayout {
notifyActiveTouchRegions();
}
- private void updateButtonTouchRegionCache() {
+ Map<View, Rect> getButtonTouchRegionCache() {
FrameLayout navBarLayout = mIsVertical
? mNavigationInflaterView.mVertical
: mNavigationInflaterView.mHorizontal;
- mButtonFullTouchableRegions = ((NearestTouchFrame) navBarLayout
+ return ((NearestTouchFrame) navBarLayout
.findViewById(R.id.nav_buttons)).getFullTouchableChildRegions();
}
- /**
- * @param includeFloatingButtons Whether to include the floating rotation and overlay button in
- * the region for all the buttons
- * @param inScreenSpace Whether to return values in screen space or window space
- * @param useNearestRegion Whether to use the nearest region instead of the actual button bounds
- * @return
- */
- Region getButtonLocations(boolean includeFloatingButtons, boolean inScreenSpace,
- boolean useNearestRegion) {
- // TODO: move this method to NavigationBar.
- // TODO: don't use member variables for temp storage like mTmpRegion.
- if (useNearestRegion && !inScreenSpace) {
- // We currently don't support getting the nearest region in anything but screen space
- useNearestRegion = false;
- }
- mTmpRegion.setEmpty();
- updateButtonTouchRegionCache();
- updateButtonLocation(getBackButton(), inScreenSpace, useNearestRegion);
- updateButtonLocation(getHomeButton(), inScreenSpace, useNearestRegion);
- updateButtonLocation(getRecentsButton(), inScreenSpace, useNearestRegion);
- updateButtonLocation(getImeSwitchButton(), inScreenSpace, useNearestRegion);
- updateButtonLocation(getAccessibilityButton(), inScreenSpace, useNearestRegion);
- if (includeFloatingButtons && mFloatingRotationButton.isVisible()) {
- // Note: this button is floating so the nearest region doesn't apply
- updateButtonLocation(mFloatingRotationButton.getCurrentView(), inScreenSpace);
- } else {
- updateButtonLocation(getRotateSuggestionButton(), inScreenSpace, useNearestRegion);
- }
- return mTmpRegion;
- }
-
- private void updateButtonLocation(ButtonDispatcher button, boolean inScreenSpace,
- boolean useNearestRegion) {
- if (button == null) {
- return;
- }
- View view = button.getCurrentView();
- if (view == null || !button.isVisible()) {
- return;
- }
- // If the button is tappable from perspective of NearestTouchFrame, then we'll
- // include the regions where the tap is valid instead of just the button layout location
- if (useNearestRegion && mButtonFullTouchableRegions.containsKey(view)) {
- mTmpRegion.op(mButtonFullTouchableRegions.get(view), Op.UNION);
- return;
- }
- updateButtonLocation(view, inScreenSpace);
- }
-
- private void updateButtonLocation(View view, boolean inScreenSpace) {
- if (inScreenSpace) {
- view.getBoundsOnScreen(mTmpBounds);
- } else {
- view.getLocationInWindow(mTmpPosition);
- mTmpBounds.set(mTmpPosition[0], mTmpPosition[1],
- mTmpPosition[0] + view.getWidth(),
- mTmpPosition[1] + view.getHeight());
- }
- mTmpRegion.op(mTmpBounds, Op.UNION);
- }
-
private void updateOrientationViews() {
mHorizontal = findViewById(R.id.horizontal);
mVertical = findViewById(R.id.vertical);
@@ -1272,7 +1167,6 @@ public class NavigationBarView extends FrameLayout {
mRotationButtonController.registerListeners();
}
- getViewTreeObserver().addOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
updateNavButtonIcons();
}
@@ -1288,8 +1182,6 @@ public class NavigationBarView extends FrameLayout {
}
mEdgeBackGestureHandler.onNavBarDetached();
- getViewTreeObserver().removeOnComputeInternalInsetsListener(
- mOnComputeInternalInsetsListener);
}
public void dump(PrintWriter pw) {
diff --git a/packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java b/packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java
index 8b8941a9112d..3709a86f2fa5 100644
--- a/packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java
+++ b/packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java
@@ -16,6 +16,8 @@
package com.android.systemui.power.dagger;
+import com.android.systemui.power.EnhancedEstimates;
+import com.android.systemui.power.EnhancedEstimatesImpl;
import com.android.systemui.power.PowerNotificationWarnings;
import com.android.systemui.power.PowerUI;
@@ -28,5 +30,9 @@ import dagger.Module;
public interface PowerModule {
/** */
@Binds
+ EnhancedEstimates bindEnhancedEstimates(EnhancedEstimatesImpl enhancedEstimates);
+
+ /** */
+ @Binds
PowerUI.WarningsUI provideWarningsUi(PowerNotificationWarnings controllerImpl);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
index 3eb4b10151d9..3e8cdf3a3592 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
@@ -420,7 +420,7 @@ class FgsManagerController @Inject constructor(
PowerExemptionManager.REASON_SYSTEM_UID,
PowerExemptionManager.REASON_DEVICE_DEMO_MODE -> UIControl.HIDE_ENTRY
- PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE,
+ PowerExemptionManager.REASON_SYSTEM_ALLOW_LISTED,
PowerExemptionManager.REASON_DEVICE_OWNER,
PowerExemptionManager.REASON_DISALLOW_APPS_CONTROL,
PowerExemptionManager.REASON_DPO_PROTECTED_APP,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 7d3df6ef6b07..5d2060d8043e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -47,9 +47,10 @@ public class QSContainerImpl extends FrameLayout implements Dumpable {
private QSCustomizer mQSCustomizer;
private NonInterceptingScrollView mQSPanelContainer;
- private int mSideMargins;
+ private int mHorizontalMargins;
+ private int mTilesPageMargin;
private boolean mQsDisabled;
- private int mContentPadding = -1;
+ private int mContentHorizontalPadding = -1;
private boolean mClippingEnabled;
public QSContainerImpl(Context context, AttributeSet attrs) {
@@ -145,12 +146,17 @@ public class QSContainerImpl extends FrameLayout implements Dumpable {
mQSPanelContainer.getPaddingEnd(),
bottomPadding);
- int sideMargins = getResources().getDimensionPixelSize(R.dimen.notification_side_paddings);
- int padding = getResources().getDimensionPixelSize(
- R.dimen.notification_shade_content_margin_horizontal);
- boolean marginsChanged = padding != mContentPadding || sideMargins != mSideMargins;
- mContentPadding = padding;
- mSideMargins = sideMargins;
+ int horizontalMargins = getResources().getDimensionPixelSize(R.dimen.qs_horizontal_margin);
+ int horizontalPadding = getResources().getDimensionPixelSize(
+ R.dimen.qs_content_horizontal_padding);
+ int tilesPageMargin = getResources().getDimensionPixelSize(
+ R.dimen.qs_tiles_page_horizontal_margin);
+ boolean marginsChanged = horizontalPadding != mContentHorizontalPadding
+ || horizontalMargins != mHorizontalMargins
+ || tilesPageMargin != mTilesPageMargin;
+ mContentHorizontalPadding = horizontalPadding;
+ mHorizontalMargins = horizontalMargins;
+ mTilesPageMargin = tilesPageMargin;
if (marginsChanged) {
updatePaddingsAndMargins(qsPanelController, quickStatusBarHeaderController);
}
@@ -198,22 +204,22 @@ public class QSContainerImpl extends FrameLayout implements Dumpable {
// Only padding for FooterActionsView, no margin. That way, the background goes
// all the way to the edge.
LayoutParams lp = (LayoutParams) view.getLayoutParams();
- lp.rightMargin = mSideMargins;
- lp.leftMargin = mSideMargins;
+ lp.rightMargin = mHorizontalMargins;
+ lp.leftMargin = mHorizontalMargins;
}
if (view == mQSPanelContainer) {
// QS panel lays out some of its content full width
- qsPanelController.setContentMargins(mContentPadding, mContentPadding);
- // Set it as double the side margin (to simulate end margin of current page +
- // start margin of next page).
- qsPanelController.setPageMargin(mSideMargins);
+ qsPanelController.setContentMargins(mContentHorizontalPadding,
+ mContentHorizontalPadding);
+ qsPanelController.setPageMargin(mTilesPageMargin);
} else if (view == mHeader) {
- quickStatusBarHeaderController.setContentMargins(mContentPadding, mContentPadding);
+ quickStatusBarHeaderController.setContentMargins(mContentHorizontalPadding,
+ mContentHorizontalPadding);
} else {
view.setPaddingRelative(
- mContentPadding,
+ mContentHorizontalPadding,
view.getPaddingTop(),
- mContentPadding,
+ mContentHorizontalPadding,
view.getPaddingBottom());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
index 772e9faf6feb..248c78e557cc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
@@ -147,16 +147,9 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
if (mController.getWalletClient().isWalletServiceAvailable()
&& mController.getWalletClient().isWalletFeatureAvailable()) {
if (mSelectedCard != null) {
- if (isDeviceLocked) {
- state.state = Tile.STATE_INACTIVE;
- state.secondaryLabel =
- mContext.getString(R.string.wallet_secondary_label_device_locked);
- state.sideViewCustomDrawable = null;
- } else {
- state.state = Tile.STATE_ACTIVE;
- state.secondaryLabel = mSelectedCard.getContentDescription();
- state.sideViewCustomDrawable = mCardViewDrawable;
- }
+ state.state = isDeviceLocked ? Tile.STATE_INACTIVE : Tile.STATE_ACTIVE;
+ state.secondaryLabel = mSelectedCard.getContentDescription();
+ state.sideViewCustomDrawable = mCardViewDrawable;
} else {
state.state = Tile.STATE_INACTIVE;
state.secondaryLabel =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
index f4dd415cb678..d99c1d1daf7e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
@@ -92,15 +92,15 @@ public abstract class SensorPrivacyToggleTile extends QSTileImpl<QSTile.BooleanS
@Override
protected void handleClick(@Nullable View view) {
- if (mKeyguard.isMethodSecure() && mKeyguard.isShowing()) {
- mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
- mSensorPrivacyController.setSensorBlocked(QS_TILE, getSensorId(),
- !mSensorPrivacyController.isSensorBlocked(getSensorId()));
- });
+ boolean blocked = mSensorPrivacyController.isSensorBlocked(getSensorId());
+ if (mSensorPrivacyController.requiresAuthentication()
+ && mKeyguard.isMethodSecure()
+ && mKeyguard.isShowing()) {
+ mActivityStarter.postQSRunnableDismissingKeyguard(() ->
+ mSensorPrivacyController.setSensorBlocked(QS_TILE, getSensorId(), !blocked));
return;
}
- mSensorPrivacyController.setSensorBlocked(QS_TILE, getSensorId(),
- !mSensorPrivacyController.isSensorBlocked(getSensorId()));
+ mSensorPrivacyController.setSensorBlocked(QS_TILE, getSensorId(), !blocked);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
index dae375ad7cc7..2d1d8b75b1bc 100644
--- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
@@ -134,7 +134,9 @@ class SensorUseStartedActivity @Inject constructor(
override fun onClick(dialog: DialogInterface?, which: Int) {
when (which) {
BUTTON_POSITIVE -> {
- if (keyguardStateController.isMethodSecure && keyguardStateController.isShowing) {
+ if (sensorPrivacyController.requiresAuthentication() &&
+ keyguardStateController.isMethodSecure &&
+ keyguardStateController.isShowing) {
keyguardDismissUtil.executeWhenUnlocked({
bgHandler.postDelayed({
disableSensorPrivacy()
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserContextProvider.kt b/packages/SystemUI/src/com/android/systemui/settings/UserContextProvider.kt
index 27af15222327..dae851252271 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserContextProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserContextProvider.kt
@@ -23,4 +23,10 @@ import android.content.Context
*/
interface UserContextProvider {
val userContext: Context
+
+ /**
+ * Creates the {@code context} with the current user.
+ * @see Context#createContextAsUser(UserHandle, int)
+ */
+ fun createCurrentUserContext(context: Context): Context
} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
index 3a6248b793d0..80d5f1681a79 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
@@ -127,6 +127,12 @@ class UserTrackerImpl internal constructor(
}
}
+ override fun createCurrentUserContext(context: Context): Context {
+ synchronized(mutex) {
+ return context.createContextAsUser(userHandle, 0)
+ }
+ }
+
private fun setUserIdInternal(user: Int): Pair<Context, List<UserInfo>> {
val profiles = userManager.getProfiles(user)
val handle = UserHandle(user)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
index ebd610bb0af4..0c9e1ec1ff77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
@@ -180,13 +180,6 @@ public interface NotificationShadeWindowController extends RemoteInputController
default void setRequestTopUi(boolean requestTopUi, String componentTag) {}
/**
- * Under low light conditions, we might want to increase the display brightness on devices that
- * don't have an IR camera.
- * @param brightness float from 0 to 1 or {@code LayoutParams.BRIGHTNESS_OVERRIDE_NONE}
- */
- default void setFaceAuthDisplayBrightness(float brightness) {}
-
- /**
* If {@link LightRevealScrim} obscures the UI.
* @param opaque if the scrim is opaque
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
index a390e9f9b09d..15ad312b413e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
@@ -20,11 +20,13 @@ import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
import com.android.systemui.statusbar.notification.collection.render.NodeController
import com.android.systemui.statusbar.notification.dagger.PeopleHeader
+import com.android.systemui.statusbar.notification.icon.ConversationIconManager
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.PeopleNotificationType
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
@@ -39,12 +41,40 @@ import javax.inject.Inject
@CoordinatorScope
class ConversationCoordinator @Inject constructor(
private val peopleNotificationIdentifier: PeopleNotificationIdentifier,
+ private val conversationIconManager: ConversationIconManager,
@PeopleHeader peopleHeaderController: NodeController
) : Coordinator {
+ private val promotedEntriesToSummaryOfSameChannel =
+ mutableMapOf<NotificationEntry, NotificationEntry>()
+
+ private val onBeforeRenderListListener = OnBeforeRenderListListener { _ ->
+ val unimportantSummaries = promotedEntriesToSummaryOfSameChannel
+ .mapNotNull { (promoted, summary) ->
+ val originalGroup = summary.parent
+ when {
+ originalGroup == null -> null
+ originalGroup == promoted.parent -> null
+ originalGroup.parent == null -> null
+ originalGroup.summary != summary -> null
+ originalGroup.children.any { it.channel == summary.channel } -> null
+ else -> summary.key
+ }
+ }
+ conversationIconManager.setUnimportantConversations(unimportantSummaries)
+ promotedEntriesToSummaryOfSameChannel.clear()
+ }
+
private val notificationPromoter = object : NotifPromoter(TAG) {
override fun shouldPromoteToTopLevel(entry: NotificationEntry): Boolean {
- return entry.channel?.isImportantConversation == true
+ val shouldPromote = entry.channel?.isImportantConversation == true
+ if (shouldPromote) {
+ val summary = entry.parent?.summary
+ if (summary != null && entry.channel == summary.channel) {
+ promotedEntriesToSummaryOfSameChannel[entry] = summary
+ }
+ }
+ return shouldPromote
}
}
@@ -67,6 +97,7 @@ class ConversationCoordinator @Inject constructor(
override fun attach(pipeline: NotifPipeline) {
pipeline.addPromoter(notificationPromoter)
+ pipeline.addOnBeforeRenderListListener(onBeforeRenderListListener)
}
private fun isConversation(entry: ListEntry): Boolean =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 34c8044ef0d3..d96590a82547 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -70,6 +70,8 @@ import com.android.systemui.statusbar.notification.collection.render.GroupMember
import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager;
import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
+import com.android.systemui.statusbar.notification.icon.ConversationIconManager;
+import com.android.systemui.statusbar.notification.icon.IconManager;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.init.NotificationsControllerImpl;
import com.android.systemui.statusbar.notification.init.NotificationsControllerStub;
@@ -370,6 +372,10 @@ public interface NotificationsModule {
/** */
@Binds
+ ConversationIconManager bindConversationIconManager(IconManager iconManager);
+
+ /** */
+ @Binds
BindEventManager bindBindEventManagerImpl(BindEventManagerImpl bindEventManagerImpl);
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index 5375ac345e50..d8965418b4c4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -27,6 +27,7 @@ import android.view.View
import android.widget.ImageView
import com.android.internal.statusbar.StatusBarIcon
import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.notification.InflationException
import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -44,11 +45,14 @@ import javax.inject.Inject
* TODO: Much of this code was copied whole-sale in order to get it out of NotificationEntry.
* Long-term, it should probably live somewhere in the content inflation pipeline.
*/
+@SysUISingleton
class IconManager @Inject constructor(
private val notifCollection: CommonNotifCollection,
private val launcherApps: LauncherApps,
private val iconBuilder: IconBuilder
-) {
+) : ConversationIconManager {
+ private var unimportantConversationKeys: Set<String> = emptySet()
+
fun attach() {
notifCollection.addCollectionListener(entryListener)
}
@@ -63,16 +67,8 @@ class IconManager @Inject constructor(
}
override fun onRankingApplied() {
- // When the sensitivity changes OR when the isImportantConversation status changes,
- // we need to update the icons
- for (entry in notifCollection.allNotifs) {
- val isImportant = isImportantConversation(entry)
- if (entry.icons.areIconsAvailable &&
- isImportant != entry.icons.isImportantConversation) {
- updateIconsSafe(entry)
- }
- entry.icons.isImportantConversation = isImportant
- }
+ // rankings affect whether a conversation is important, which can change the icons
+ recalculateForImportantConversationChange()
}
}
@@ -80,6 +76,18 @@ class IconManager @Inject constructor(
entry -> updateIconsSafe(entry)
}
+ private fun recalculateForImportantConversationChange() {
+ for (entry in notifCollection.allNotifs) {
+ val isImportant = isImportantConversation(entry)
+ if (entry.icons.areIconsAvailable &&
+ isImportant != entry.icons.isImportantConversation
+ ) {
+ updateIconsSafe(entry)
+ }
+ entry.icons.isImportantConversation = isImportant
+ }
+ }
+
/**
* Inflate icon views for each icon variant and assign appropriate icons to them. Stores the
* result in [NotificationEntry.getIcons].
@@ -306,8 +314,28 @@ class IconManager @Inject constructor(
}
private fun isImportantConversation(entry: NotificationEntry): Boolean {
- return entry.ranking.channel != null && entry.ranking.channel.isImportantConversation
+ return entry.ranking.channel != null &&
+ entry.ranking.channel.isImportantConversation &&
+ entry.key !in unimportantConversationKeys
+ }
+
+ override fun setUnimportantConversations(keys: Collection<String>) {
+ val newKeys = keys.toSet()
+ val changed = unimportantConversationKeys != newKeys
+ unimportantConversationKeys = newKeys
+ if (changed) {
+ recalculateForImportantConversationChange()
+ }
}
}
-private const val TAG = "IconManager" \ No newline at end of file
+private const val TAG = "IconManager"
+
+interface ConversationIconManager {
+ /**
+ * Sets the complete current set of notification keys which should (for the purposes of icon
+ * presentation) be considered unimportant. This tells the icon manager to remove the avatar
+ * of a group from which the priority notification has been removed.
+ */
+ fun setUnimportantConversations(keys: Collection<String>)
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
index 5646545dcd23..cff42f29a626 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
@@ -18,6 +18,8 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
@@ -72,7 +74,7 @@ private class KeyguardNotificationVisibilityProviderImpl @Inject constructor(
private val lockscreenUserManager: NotificationLockscreenUserManager,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val highPriorityProvider: HighPriorityProvider,
- private val statusBarStateController: StatusBarStateController,
+ private val statusBarStateController: SysuiStatusBarStateController,
private val broadcastDispatcher: BroadcastDispatcher,
private val secureSettings: SecureSettings,
private val globalSettings: GlobalSettings
@@ -105,7 +107,8 @@ private class KeyguardNotificationVisibilityProviderImpl @Inject constructor(
if (uri == showSilentNotifsUri) {
readShowSilentNotificationSetting()
}
- if (keyguardStateController.isShowing) {
+ if (statusBarStateController.getCurrentOrUpcomingState()
+ == StatusBarState.KEYGUARD) {
notifyStateChanged("Settings $uri changed")
}
}
@@ -131,13 +134,14 @@ private class KeyguardNotificationVisibilityProviderImpl @Inject constructor(
// register (maybe) public mode changed callbacks:
statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
- override fun onStateChanged(state: Int) {
- notifyStateChanged("onStatusBarStateChanged")
+ override fun onUpcomingStateChanged(state: Int) {
+ notifyStateChanged("onStatusBarUpcomingStateChanged")
}
})
broadcastDispatcher.registerReceiver(object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
- if (keyguardStateController.isShowing) {
+ if (statusBarStateController.getCurrentOrUpcomingState()
+ == StatusBarState.KEYGUARD) {
// maybe public mode changed
notifyStateChanged(intent.action!!)
}
@@ -159,17 +163,28 @@ private class KeyguardNotificationVisibilityProviderImpl @Inject constructor(
override fun shouldHideNotification(entry: NotificationEntry): Boolean = when {
// Keyguard state doesn't matter if the keyguard is not showing.
- !keyguardStateController.isShowing -> false
+ statusBarStateController.getCurrentOrUpcomingState() != StatusBarState.KEYGUARD -> false
// Notifications not allowed on the lockscreen, always hide.
!lockscreenUserManager.shouldShowLockscreenNotifications() -> true
// User settings do not allow this notification on the lockscreen, so hide it.
userSettingsDisallowNotification(entry) -> true
+ // if entry is silent, apply custom logic to see if should hide
+ shouldHideIfEntrySilent(entry) -> true
+ else -> false
+ }
+
+ private fun shouldHideIfEntrySilent(entry: ListEntry): Boolean = when {
+ // Show if high priority (not hidden)
+ highPriorityProvider.isHighPriority(entry) -> false
+ // Ambient notifications are hidden always from lock screen
+ entry.representativeEntry?.isAmbient == true -> true
+ // [Now notification is silent]
+ // Hide regardless of parent priority if user wants silent notifs hidden
+ hideSilentNotificationsOnLockscreen -> true
// Parent priority is high enough to be shown on the lockscreen, do not hide.
- entry.parent?.let(::priorityExceedsLockscreenShowingThreshold) == true -> false
- // Entry priority is high enough to be shown on the lockscreen, do not hide.
- priorityExceedsLockscreenShowingThreshold(entry) -> false
- // Priority is too low, hide.
- else -> true
+ entry.parent?.let(::shouldHideIfEntrySilent) == false -> false
+ // Show when silent notifications are allowed on lockscreen
+ else -> false
}
private fun userSettingsDisallowNotification(entry: NotificationEntry): Boolean {
@@ -193,11 +208,6 @@ private class KeyguardNotificationVisibilityProviderImpl @Inject constructor(
}
}
- private fun priorityExceedsLockscreenShowingThreshold(entry: ListEntry): Boolean = when {
- hideSilentNotificationsOnLockscreen -> highPriorityProvider.isHighPriority(entry)
- else -> entry.representativeEntry?.ranking?.isAmbient == false
- }
-
private fun readShowSilentNotificationSetting() {
val showSilentNotifs =
secureSettings.getBoolForUser(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS,
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 ef24d7793562..5c12671726f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -1022,6 +1022,23 @@ public class CentralSurfacesImpl extends CoreStartable implements
public void onUnlockedChanged() {
logStateToEventlog();
}
+
+ @Override
+ public void onKeyguardGoingAwayChanged() {
+ // The light reveal scrim should always be fully revealed by the time the keyguard
+ // is done going away. Double check that this is true.
+ if (!mKeyguardStateController.isKeyguardGoingAway()) {
+ if (mLightRevealScrim.getRevealAmount() != 1f) {
+ Log.e(TAG, "Keyguard is done going away, but someone left the light reveal "
+ + "scrim at reveal amount: " + mLightRevealScrim.getRevealAmount());
+ }
+
+ // If the auth ripple is still playing, let it finish.
+ if (!mAuthRippleController.isAnimatingLightRevealScrim()) {
+ mLightRevealScrim.setRevealAmount(1f);
+ }
+ }
+ }
});
startKeyguard();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 69beaf56519f..0b721383e2d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -220,7 +220,7 @@ public class KeyguardBouncer {
DejankUtils.postAfterTraversal(mShowRunnable);
}
- mCallback.onBouncerVisiblityChanged(true /* shown */);
+ mKeyguardStateController.notifyBouncerShowing(true /* showing */);
dispatchStartingToShow();
} finally {
Trace.endSection();
@@ -334,7 +334,7 @@ public class KeyguardBouncer {
}
mIsScrimmed = false;
mFalsingCollector.onBouncerHidden();
- mCallback.onBouncerVisiblityChanged(false /* shown */);
+ mKeyguardStateController.notifyBouncerShowing(false /* showing */);
cancelShowRunnable();
if (mKeyguardViewController != null) {
mKeyguardViewController.cancelDismissAction();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 83970dc15e61..629aa0317d24 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -220,7 +220,7 @@ public class KeyguardClockPositionAlgorithm {
}
}
- public float getMinStackScrollerPadding() {
+ public float getLockscreenMinStackScrollerPadding() {
if (mBypassEnabled) {
return mUnlockedStackScrollerPadding;
} else if (mIsSplitShade) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 985df423d744..74b9c71d2198 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -24,6 +24,7 @@ import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE;
import static com.android.keyguard.KeyguardClockSwitch.LARGE;
import static com.android.keyguard.KeyguardClockSwitch.SMALL;
+import static com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE;
import static com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE;
import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
@@ -325,6 +326,11 @@ public class NotificationPanelViewController extends PanelViewController {
private boolean mShouldUseSplitNotificationShade;
// The bottom padding reserved for elements of the keyguard measuring notifications
private float mKeyguardNotificationBottomPadding;
+ /**
+ * The top padding from where notification should start in lockscreen.
+ * Should be static also during animations and should match the Y of the first notification.
+ */
+ private float mKeyguardNotificationTopPadding;
// Current max allowed keyguard notifications determined by measuring the panel
private int mMaxAllowedKeyguardNotifications;
@@ -936,7 +942,7 @@ public class NotificationPanelViewController extends PanelViewController {
// the launcher icons animation starts, so use that as our
// duration.
.setDuration(unlockAnimationStartDelay)
- .setInterpolator(EMPHASIZED_DECELERATE)
+ .setInterpolator(EMPHASIZED_ACCELERATE)
.withEndAction(() -> {
instantCollapse();
mView.setAlpha(1f);
@@ -1513,7 +1519,10 @@ public class NotificationPanelViewController extends PanelViewController {
*/
@VisibleForTesting
float getSpaceForLockscreenNotifications() {
- float topPadding = mNotificationStackScrollLayoutController.getTopPadding();
+ float staticTopPadding = mClockPositionAlgorithm.getLockscreenMinStackScrollerPadding()
+ // getMinStackScrollerPadding is from the top of the screen,
+ // but we need it from the top of the NSSL.
+ - mNotificationStackScrollLayoutController.getTop();
// Space between bottom of notifications and top of lock icon or udfps background.
float lockIconPadding = mLockIconViewController.getTop();
@@ -1525,11 +1534,15 @@ public class NotificationPanelViewController extends PanelViewController {
float bottomPadding = Math.max(lockIconPadding,
Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding));
+
mKeyguardNotificationBottomPadding = bottomPadding;
+ mKeyguardNotificationTopPadding = staticTopPadding;
+ // To debug the available space, enable debug lines in this class. If you change how the
+ // available space is calculated, please also update those lines.
float availableSpace =
mNotificationStackScrollLayoutController.getHeight()
- - topPadding
+ - staticTopPadding
- bottomPadding;
return availableSpace;
}
@@ -4923,6 +4936,20 @@ public class NotificationPanelViewController extends PanelViewController {
drawDebugInfo(canvas, (int) mLockIconViewController.getTop(), Color.GRAY,
"mLockIconViewController.getTop()");
+ if (mKeyguardShowing) {
+ // Notifications have the space between those two lines.
+ drawDebugInfo(canvas,
+ mNotificationStackScrollLayoutController.getTop() +
+ (int) mKeyguardNotificationTopPadding,
+ Color.RED,
+ "NSSL.getTop() + mKeyguardNotificationTopPadding");
+
+ drawDebugInfo(canvas, mNotificationStackScrollLayoutController.getBottom() -
+ (int) mKeyguardNotificationBottomPadding,
+ Color.RED,
+ "NSSL.getBottom() - mKeyguardNotificationBottomPadding");
+ }
+
mDebugPaint.setColor(Color.CYAN);
canvas.drawLine(0, mClockPositionResult.stackScrollerPadding, mView.getWidth(),
mNotificationStackScrollLayoutController.getTopPadding(), mDebugPaint);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
index 24660b261c51..faae4bbbafd0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
@@ -111,7 +111,6 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
private final SysuiColorExtractor mColorExtractor;
private final ScreenOffAnimationController mScreenOffAnimationController;
- private float mFaceAuthDisplayBrightness = LayoutParams.BRIGHTNESS_OVERRIDE_NONE;
/**
* Layout params would be aggregated and dispatched all at once if this is > 0.
*
@@ -266,12 +265,6 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
mScreenBrightnessDoze = value / 255f;
}
- @Override
- public void setFaceAuthDisplayBrightness(float brightness) {
- mFaceAuthDisplayBrightness = brightness;
- apply(mCurrentState);
- }
-
private void setKeyguardDark(boolean dark) {
int vis = mNotificationShadeView.getSystemUiVisibility();
if (dark) {
@@ -340,7 +333,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
}
private void adjustScreenOrientation(State state) {
- if (state.isKeyguardShowingAndNotOccluded() || state.mDozing) {
+ if (state.mBouncerShowing || state.isKeyguardShowingAndNotOccluded() || state.mDozing) {
if (mKeyguardStateController.isKeyguardScreenRotationAllowed()) {
mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER;
} else {
@@ -455,7 +448,9 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
private void applyWindowLayoutParams() {
if (mDeferWindowLayoutParams == 0 && mLp != null && mLp.copyFrom(mLpChanged) != 0) {
+ Trace.beginSection("updateViewLayout");
mWindowManager.updateViewLayout(mNotificationShadeView, mLp);
+ Trace.endSection();
}
}
@@ -523,7 +518,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
if (state.mForceDozeBrightness) {
mLpChanged.screenBrightness = mScreenBrightnessDoze;
} else {
- mLpChanged.screenBrightness = mFaceAuthDisplayBrightness;
+ mLpChanged.screenBrightness = LayoutParams.BRIGHTNESS_OVERRIDE_NONE;
}
}
@@ -572,6 +567,10 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
@Override
public void setPanelVisible(boolean visible) {
+ if (mCurrentState.mPanelVisible == visible
+ && mCurrentState.mNotificationShadeFocusable == visible) {
+ return;
+ }
mCurrentState.mPanelVisible = visible;
mCurrentState.mNotificationShadeFocusable = visible;
apply(mCurrentState);
@@ -626,8 +625,14 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
@Override
public void setScrimsVisibility(int scrimsVisibility) {
+ if (scrimsVisibility == mCurrentState.mScrimsVisibility) {
+ return;
+ }
+ boolean wasExpanded = isExpanded(mCurrentState);
mCurrentState.mScrimsVisibility = scrimsVisibility;
- apply(mCurrentState);
+ if (wasExpanded != isExpanded(mCurrentState)) {
+ apply(mCurrentState);
+ }
mScrimsVisibilityListener.accept(scrimsVisibility);
}
@@ -687,6 +692,9 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
@Override
public void setPanelExpanded(boolean isExpanded) {
+ if (mCurrentState.mPanelExpanded == isExpanded) {
+ return;
+ }
mCurrentState.mPanelExpanded = isExpanded;
apply(mCurrentState);
}
@@ -703,6 +711,9 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
*/
@Override
public void setForceDozeBrightness(boolean forceDozeBrightness) {
+ if (mCurrentState.mForceDozeBrightness == forceDozeBrightness) {
+ return;
+ }
mCurrentState.mForceDozeBrightness = forceDozeBrightness;
apply(mCurrentState);
}
@@ -841,7 +852,6 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
boolean mLightRevealScrimOpaque;
boolean mForceCollapsed;
boolean mForceDozeBrightness;
- int mFaceAuthDisplayBrightness;
boolean mForceUserActivity;
boolean mLaunchingActivity;
boolean mBackdropShowing;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 935f87dc8221..c1d0769eaa44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -175,28 +175,36 @@ class UnlockedScreenOffAnimationController @Inject constructor(
.setDuration(duration.toLong())
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
.alpha(1f)
- .setListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
- aodUiAnimationPlaying = false
+ .withEndAction {
+ aodUiAnimationPlaying = false
+
+ // Lock the keyguard if it was waiting for the screen off animation to end.
+ keyguardViewMediatorLazy.get().maybeHandlePendingLock()
- // Lock the keyguard if it was waiting for the screen off animation to end.
- keyguardViewMediatorLazy.get().maybeHandlePendingLock()
+ // Tell the CentralSurfaces to become keyguard for real - we waited on that
+ // since it is slow and would have caused the animation to jank.
+ mCentralSurfaces.updateIsKeyguard()
- // Tell the CentralSurfaces to become keyguard for real - we waited on that
- // since it is slow and would have caused the animation to jank.
- mCentralSurfaces.updateIsKeyguard()
+ // Run the callback given to us by the KeyguardVisibilityHelper.
+ after.run()
- // Run the callback given to us by the KeyguardVisibilityHelper.
- after.run()
+ // Done going to sleep, reset this flag.
+ decidedToAnimateGoingToSleep = null
- // Done going to sleep, reset this flag.
+ // We need to unset the listener. These are persistent for future animators
+ keyguardView.animate().setListener(null)
+ interactionJankMonitor.end(CUJ_SCREEN_OFF_SHOW_AOD)
+ }
+ .setListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationCancel(animation: Animator?) {
+ // If we're cancelled, reset state flags/listeners. The end action above
+ // will not be called, which is what we want since that will finish the
+ // screen off animation and show the lockscreen, which we don't want if we
+ // were cancelled.
+ aodUiAnimationPlaying = false
decidedToAnimateGoingToSleep = null
- // We need to unset the listener. These are persistent for future animators
keyguardView.animate().setListener(null)
- interactionJankMonitor.end(CUJ_SCREEN_OFF_SHOW_AOD)
- }
- override fun onAnimationCancel(animation: Animator?) {
interactionJankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java
index 1e73d593c8de..eb08f37503c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java
@@ -37,6 +37,11 @@ public interface IndividualSensorPrivacyController extends
void suppressSensorPrivacyReminders(int sensor, boolean suppress);
+ /**
+ * @return whether lock screen authentication is required to change the toggle state
+ */
+ boolean requiresAuthentication();
+
interface Callback {
void onSensorBlockedChanged(@Sensor int sensor, boolean blocked);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
index e4c444da5da9..fffd839fcf11 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
@@ -37,6 +37,7 @@ public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPr
private final @NonNull SensorPrivacyManager mSensorPrivacyManager;
private final SparseBooleanArray mSoftwareToggleState = new SparseBooleanArray();
private final SparseBooleanArray mHardwareToggleState = new SparseBooleanArray();
+ private Boolean mRequiresAuthentication;
private final Set<Callback> mCallbacks = new ArraySet<>();
public IndividualSensorPrivacyControllerImpl(
@@ -96,6 +97,11 @@ public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPr
}
@Override
+ public boolean requiresAuthentication() {
+ return mSensorPrivacyManager.requiresAuthentication();
+ }
+
+ @Override
public void addCallback(@NonNull Callback listener) {
mCallbacks.add(listener);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
index 233778dbfeb6..15ee553da457 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
@@ -46,6 +46,11 @@ public interface KeyguardStateController extends CallbackController<Callback> {
boolean isShowing();
/**
+ * Whether the bouncer (PIN/password entry) is currently visible.
+ */
+ boolean isBouncerShowing();
+
+ /**
* If swiping up will unlock without asking for a password.
* @see #isUnlocked()
*/
@@ -186,6 +191,8 @@ public interface KeyguardStateController extends CallbackController<Callback> {
default void notifyKeyguardDoneFading() {}
/** **/
default void notifyKeyguardState(boolean showing, boolean occluded) {}
+ /** **/
+ default void notifyBouncerShowing(boolean showing) {}
/**
* Updates the keyguard state to reflect that it's in the process of being dismissed, either by
@@ -231,6 +238,11 @@ public interface KeyguardStateController extends CallbackController<Callback> {
default void onKeyguardShowingChanged() {}
/**
+ * Called when the bouncer (PIN/password entry) is shown or hidden.
+ */
+ default void onBouncerShowingChanged() {}
+
+ /**
* Triggered when the device was just unlocked and the lock screen is being dismissed.
*/
default void onKeyguardFadingAwayChanged() {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index be5da377e496..77e285d1e15d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -63,6 +63,7 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum
private boolean mCanDismissLockScreen;
private boolean mShowing;
+ private boolean mBouncerShowing;
private boolean mSecure;
private boolean mOccluded;
@@ -153,6 +154,11 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum
}
@Override
+ public boolean isBouncerShowing() {
+ return mBouncerShowing;
+ }
+
+ @Override
public boolean isMethodSecure() {
return mSecure;
}
@@ -328,6 +334,15 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum
}
@Override
+ public void notifyBouncerShowing(boolean showing) {
+ if (mBouncerShowing != showing) {
+ mBouncerShowing = showing;
+
+ new ArrayList<>(mCallbacks).forEach(Callback::onBouncerShowingChanged);
+ }
+ }
+
+ @Override
public void notifyPanelFlingEnd() {
mFlingingToDismissKeyguard = false;
mFlingingToDismissKeyguardDuringSwipeGesture = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 37517219f103..5a33603d81ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -269,8 +269,12 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
super.onEnd(animation);
if (animation.getTypeMask() == WindowInsets.Type.ime()) {
mEntry.mRemoteEditImeAnimatingAway = false;
- mEntry.mRemoteEditImeVisible =
- mEditText.getRootWindowInsets().isVisible(WindowInsets.Type.ime());
+ WindowInsets editTextRootWindowInsets = mEditText.getRootWindowInsets();
+ if (editTextRootWindowInsets == null) {
+ Log.w(TAG, "onEnd called on detached view", new Exception());
+ }
+ mEntry.mRemoteEditImeVisible = editTextRootWindowInsets != null
+ && editTextRootWindowInsets.isVisible(WindowInsets.Type.ime());
if (!mEntry.mRemoteEditImeVisible && !mEditText.mShowImeOnInputConnection) {
mController.removeRemoteInput(mEntry, mToken);
}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
index b7f90a479518..4685c148e7e5 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
@@ -38,7 +38,6 @@ import com.android.systemui.doze.DozeHost;
import com.android.systemui.plugins.qs.QSFactory;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.EnhancedEstimates;
-import com.android.systemui.power.EnhancedEstimatesImpl;
import com.android.systemui.power.dagger.PowerModule;
import com.android.systemui.qs.dagger.QSModule;
import com.android.systemui.qs.tileimpl.QSFactoryImpl;
@@ -102,9 +101,6 @@ public abstract class TvSystemUIModule {
}
@Binds
- abstract EnhancedEstimates bindEnhancedEstimates(EnhancedEstimatesImpl enhancedEstimates);
-
- @Binds
abstract NotificationLockscreenUserManager bindNotificationLockscreenUserManager(
NotificationLockscreenUserManagerImpl notificationLockscreenUserManager);
diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java
index 5b5dca30620a..d54de3fa9a3f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java
@@ -21,9 +21,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
-import android.content.res.TypedArray;
import android.provider.Settings;
-import android.view.ContextThemeWrapper;
import android.view.DisplayCutout;
import com.android.internal.policy.SystemBarUtils;
@@ -35,6 +33,8 @@ import java.util.function.Consumer;
public class Utils {
+ private static Boolean sUseQsMediaPlayer = null;
+
/**
* Allows lambda iteration over a list. It is done in reverse order so it is safe
* to add or remove items during the iteration. Skips over null items.
@@ -81,9 +81,16 @@ public class Utils {
* Off by default, but can be disabled by setting to 0
*/
public static boolean useQsMediaPlayer(Context context) {
- int flag = Settings.Global.getInt(context.getContentResolver(),
- Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1);
- return flag > 0;
+ // TODO(b/192412820): Replace SHOW_MEDIA_ON_QUICK_SETTINGS with compile-time value
+ // Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS can't be toggled at runtime, so simply
+ // cache the first result we fetch and use that going forward. Do this to avoid unnecessary
+ // binder calls which may happen on the critical path.
+ if (sUseQsMediaPlayer == null) {
+ int flag = Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1);
+ sUseQsMediaPlayer = flag > 0;
+ }
+ return sUseQsMediaPlayer;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
index acff8712e92e..ebdddbf66091 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
@@ -180,7 +180,7 @@ public class QuickAccessWalletController {
int iconSizePx = mContext.getResources().getDimensionPixelSize(R.dimen.wallet_icon_size);
GetWalletCardsRequest request =
new GetWalletCardsRequest(cardWidth, cardHeight, iconSizePx, /* maxCards= */ 1);
- mQuickAccessWalletClient.getWalletCards(mExecutor, request, cardsRetriever);
+ mQuickAccessWalletClient.getWalletCards(mCallbackExecutor, request, cardsRetriever);
}
/**
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
index 747649006b45..39cc34bb7e26 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
@@ -18,6 +18,7 @@ package com.android.keyguard
import android.content.ContentResolver
import android.database.ContentObserver
+import android.hardware.biometrics.BiometricFaceConstants
import android.net.Uri
import android.os.Handler
import android.os.UserHandle
@@ -44,18 +45,20 @@ class ActiveUnlockConfigTest : SysuiTestCase() {
private val fakeWakeUri = Uri.Builder().appendPath("wake").build()
private val fakeUnlockIntentUri = Uri.Builder().appendPath("unlock-intent").build()
private val fakeBioFailUri = Uri.Builder().appendPath("bio-fail").build()
+ private val fakeFaceErrorsUri = Uri.Builder().appendPath("face-errors").build()
+ private val fakeFaceAcquiredUri = Uri.Builder().appendPath("face-acquired").build()
+ private val fakeUnlockIntentBioEnroll = Uri.Builder().appendPath("unlock-intent-bio").build()
@Mock
private lateinit var secureSettings: SecureSettings
-
@Mock
private lateinit var contentResolver: ContentResolver
-
@Mock
private lateinit var handler: Handler
-
@Mock
private lateinit var dumpManager: DumpManager
+ @Mock
+ private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Captor
private lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver>
@@ -72,6 +75,13 @@ class ActiveUnlockConfigTest : SysuiTestCase() {
.thenReturn(fakeUnlockIntentUri)
`when`(secureSettings.getUriFor(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL))
.thenReturn(fakeBioFailUri)
+ `when`(secureSettings.getUriFor(Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS))
+ .thenReturn(fakeFaceErrorsUri)
+ `when`(secureSettings.getUriFor(Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO))
+ .thenReturn(fakeFaceAcquiredUri)
+ `when`(secureSettings.getUriFor(
+ Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED))
+ .thenReturn(fakeUnlockIntentBioEnroll)
activeUnlockConfig = ActiveUnlockConfig(
handler,
@@ -99,12 +109,7 @@ class ActiveUnlockConfigTest : SysuiTestCase() {
// WHEN unlock on wake is allowed
`when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_WAKE,
0, 0)).thenReturn(1)
- settingsObserverCaptor.value.onChange(
- false,
- listOf(fakeWakeUri),
- 0,
- 0
- )
+ updateSetting(fakeWakeUri)
// THEN active unlock triggers allowed on: wake, unlock-intent, and biometric failure
assertTrue(
@@ -134,12 +139,7 @@ class ActiveUnlockConfigTest : SysuiTestCase() {
// WHEN unlock on biometric failed is allowed
`when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT,
0, 0)).thenReturn(1)
- settingsObserverCaptor.value.onChange(
- false,
- listOf(fakeUnlockIntentUri),
- 0,
- 0
- )
+ updateSetting(fakeUnlockIntentUri)
// THEN active unlock triggers allowed on: biometric failure ONLY
assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -154,19 +154,19 @@ class ActiveUnlockConfigTest : SysuiTestCase() {
fun testOnBioFailSettingChanged() {
verifyRegisterSettingObserver()
- // GIVEN no active unlock settings enabled
+ // GIVEN no active unlock settings enabled and triggering unlock intent on biometric
+ // enrollment setting is disabled (empty string is disabled, null would use the default)
+ `when`(secureSettings.getStringForUser(
+ Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
+ 0)).thenReturn("")
+ updateSetting(fakeUnlockIntentBioEnroll)
assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL))
// WHEN unlock on biometric failed is allowed
`when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
0, 0)).thenReturn(1)
- settingsObserverCaptor.value.onChange(
- false,
- listOf(fakeBioFailUri),
- 0,
- 0
- )
+ updateSetting(fakeBioFailUri)
// THEN active unlock triggers allowed on: biometric failure ONLY
assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -177,21 +177,146 @@ class ActiveUnlockConfigTest : SysuiTestCase() {
ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL))
}
- private fun verifyRegisterSettingObserver() {
- verify(contentResolver).registerContentObserver(
- eq(fakeWakeUri),
- eq(false),
- capture(settingsObserverCaptor),
- eq(UserHandle.USER_ALL))
+ @Test
+ fun testFaceErrorSettingsChanged() {
+ verifyRegisterSettingObserver()
- verify(contentResolver).registerContentObserver(
- eq(fakeUnlockIntentUri),
- eq(false),
- capture(settingsObserverCaptor),
- eq(UserHandle.USER_ALL))
+ // GIVEN unlock on biometric fail
+ `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
+ 0, 0)).thenReturn(1)
+ updateSetting(fakeBioFailUri)
+
+ // WHEN face error timeout (3), allow trigger active unlock
+ `when`(secureSettings.getStringForUser(Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS,
+ 0)).thenReturn("3")
+ updateSetting(fakeFaceAcquiredUri)
+
+ // THEN active unlock triggers allowed on error TIMEOUT
+ assertTrue(activeUnlockConfig.shouldRequestActiveUnlockOnFaceError(
+ BiometricFaceConstants.FACE_ERROR_TIMEOUT))
+
+ assertFalse(activeUnlockConfig.shouldRequestActiveUnlockOnFaceError(
+ BiometricFaceConstants.FACE_ERROR_CANCELED))
+ }
+
+ @Test
+ fun testFaceAcquiredSettingsChanged() {
+ verifyRegisterSettingObserver()
+
+ // GIVEN unlock on biometric fail
+ `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
+ 0, 0)).thenReturn(1)
+ updateSetting(fakeBioFailUri)
+
+ // WHEN face acquiredMsg DARK_GLASSESand MOUTH_COVERING are allowed to trigger
+ `when`(secureSettings.getStringForUser(Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO,
+ 0)).thenReturn(
+ "${BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED}" +
+ "|${BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED}")
+ updateSetting(fakeFaceAcquiredUri)
+
+ // THEN active unlock triggers allowed on acquired messages DARK_GLASSES & MOUTH_COVERING
+ assertTrue(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
+ BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED))
+ assertTrue(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
+ BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED))
+
+ assertFalse(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
+ BiometricFaceConstants.FACE_ACQUIRED_GOOD))
+ assertFalse(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
+ BiometricFaceConstants.FACE_ACQUIRED_NOT_DETECTED))
+ }
+
+ @Test
+ fun testTriggerOnUnlockIntentWhenBiometricEnrolledNone() {
+ verifyRegisterSettingObserver()
+
+ // GIVEN unlock on biometric fail
+ `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
+ 0, 0)).thenReturn(1)
+ updateSetting(fakeBioFailUri)
+
+ // GIVEN fingerprint and face are NOT enrolled
+ activeUnlockConfig.keyguardUpdateMonitor = keyguardUpdateMonitor
+ `when`(keyguardUpdateMonitor.isFaceEnrolled()).thenReturn(false)
+ `when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(false)
+
+ // WHEN unlock intent is allowed when NO biometrics are enrolled (0)
+ `when`(secureSettings.getStringForUser(
+ Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
+ 0)).thenReturn("${ActiveUnlockConfig.BIOMETRIC_TYPE_NONE}")
+ updateSetting(fakeUnlockIntentBioEnroll)
+
+ // THEN active unlock triggers allowed on unlock intent
+ assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+ ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT))
+ }
+
+ @Test
+ fun testTriggerOnUnlockIntentWhenBiometricEnrolledFingerprintOrFaceOnly() {
+ verifyRegisterSettingObserver()
+
+ // GIVEN unlock on biometric fail
+ `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
+ 0, 0)).thenReturn(1)
+ updateSetting(fakeBioFailUri)
+
+ // GIVEN fingerprint and face are both enrolled
+ activeUnlockConfig.keyguardUpdateMonitor = keyguardUpdateMonitor
+ `when`(keyguardUpdateMonitor.isFaceEnrolled()).thenReturn(true)
+ `when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(true)
+
+ // WHEN unlock intent is allowed when ONLY fingerprint is enrolled or NO biometircs
+ // are enrolled
+ `when`(secureSettings.getStringForUser(
+ Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
+ 0)).thenReturn(
+ "${ActiveUnlockConfig.BIOMETRIC_TYPE_ANY_FACE}" +
+ "|${ActiveUnlockConfig.BIOMETRIC_TYPE_ANY_FINGERPRINT}")
+ updateSetting(fakeUnlockIntentBioEnroll)
+
+ // THEN active unlock triggers NOT allowed on unlock intent
+ assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+ ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT))
+
+ // WHEN fingerprint ONLY enrolled
+ `when`(keyguardUpdateMonitor.isFaceEnrolled()).thenReturn(false)
+ `when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(true)
+
+ // THEN active unlock triggers allowed on unlock intent
+ assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+ ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT))
+
+ // WHEN face ONLY enrolled
+ `when`(keyguardUpdateMonitor.isFaceEnrolled()).thenReturn(true)
+ `when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(false)
+
+ // THEN active unlock triggers allowed on unlock intent
+ assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+ ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT))
+ }
+
+ private fun updateSetting(uri: Uri) {
+ settingsObserverCaptor.value.onChange(
+ false,
+ listOf(uri),
+ 0,
+ 0 /* flags */
+ )
+ }
+
+ private fun verifyRegisterSettingObserver() {
+ verifyRegisterSettingObserver(fakeWakeUri)
+ verifyRegisterSettingObserver(fakeUnlockIntentUri)
+ verifyRegisterSettingObserver(fakeBioFailUri)
+ verifyRegisterSettingObserver(fakeFaceErrorsUri)
+ verifyRegisterSettingObserver(fakeFaceAcquiredUri)
+ verifyRegisterSettingObserver(fakeUnlockIntentBioEnroll)
+ }
+ private fun verifyRegisterSettingObserver(uri: Uri) {
verify(contentResolver).registerContentObserver(
- eq(fakeBioFailUri),
+ eq(uri),
eq(false),
capture(settingsObserverCaptor),
eq(UserHandle.USER_ALL))
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index aff94eb7aef5..a1e23a201b72 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -25,7 +25,6 @@ import com.android.internal.widget.LockPatternUtils
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.util.concurrency.DelayableExecutor
import org.junit.Before
import org.junit.Test
@@ -63,7 +62,7 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() {
@Mock
lateinit var falsingCollector: FalsingCollector
@Mock
- lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
+ lateinit var keyguardViewController: KeyguardViewController
@Mock
private lateinit var mKeyguardMessageArea: KeyguardMessageArea
@Mock
@@ -92,13 +91,13 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() {
mainExecutor,
mContext.resources,
falsingCollector,
- statusBarKeyguardViewManager
+ keyguardViewController
)
}
@Test
fun testFocusWhenBouncerIsShown() {
- Mockito.`when`(statusBarKeyguardViewManager.isBouncerShowing).thenReturn(true)
+ Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(true)
Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
keyguardPasswordView.post { verify(keyguardPasswordView).requestFocus() }
@@ -106,7 +105,7 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() {
@Test
fun testDoNotFocusWhenBouncerIsHidden() {
- Mockito.`when`(statusBarKeyguardViewManager.isBouncerShowing).thenReturn(false)
+ Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(false)
Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
verify(keyguardPasswordView, never()).requestFocus()
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 7b7dfdce90b2..4d3343059718 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -51,7 +51,6 @@ import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -125,7 +124,7 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
@Mock
private SessionTracker mSessionTracker;
@Mock
- private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private KeyguardViewController mKeyguardViewController;
private Configuration mConfiguration;
private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
@@ -153,7 +152,7 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
(KeyguardPasswordView) mKeyguardPasswordView, mKeyguardUpdateMonitor,
SecurityMode.Password, mLockPatternUtils, null,
mKeyguardMessageAreaControllerFactory, null, null, mEmergencyButtonController,
- null, mock(Resources.class), null, mStatusBarKeyguardViewManager);
+ null, mock(Resources.class), null, mKeyguardViewController);
mKeyguardSecurityContainerController = new KeyguardSecurityContainerController.Factory(
mView, mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
index 57f36172c998..92c2a1b9b23a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
@@ -29,6 +29,7 @@ fun SensorLocationInternal.asFingerprintSensorProperties(
@FingerprintSensorProperties.SensorType sensorType: Int =
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
maxEnrollmentsPerUser: Int = 1,
+ halControlsIllumination: Boolean = true,
info: List<ComponentInfoInternal> = listOf(ComponentInfoInternal("a", "b", "c", "d", "e")),
resetLockoutRequiresHardwareAuthToken: Boolean = false
) = FingerprintSensorPropertiesInternal(
@@ -37,6 +38,7 @@ fun SensorLocationInternal.asFingerprintSensorProperties(
maxEnrollmentsPerUser,
info,
sensorType,
+ halControlsIllumination,
resetLockoutRequiresHardwareAuthToken,
listOf(this)
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
index 102f37c4a037..dec2b82ed88f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
@@ -185,6 +185,7 @@ class SidefpsControllerTest : SysuiTestCase() {
5 /* maxEnrollmentsPerUser */,
listOf() /* componentInfo */,
FingerprintSensorProperties.TYPE_POWER_BUTTON,
+ true /* halControlsIllumination */,
true /* resetLockoutRequiresHardwareAuthToken */,
locations
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index a57b011d7125..fc5ccbcb4a76 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -65,6 +65,7 @@ import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
import org.mockito.Mockito.`when` as whenever
+private const val HAL_CONTROLS_ILLUMINATION = true
private const val REQUEST_ID = 2L
// Dimensions for the current display resolution.
@@ -129,8 +130,8 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
statusBarStateController, panelExpansionStateManager, statusBarKeyguardViewManager,
keyguardUpdateMonitor, dialogManager, dumpManager, transitionController,
configurationController, systemClock, keyguardStateController,
- unlockedScreenOffAnimationController, hbmProvider, REQUEST_ID, reason,
- controllerCallback, onTouch, activityLaunchAnimator)
+ unlockedScreenOffAnimationController, HAL_CONTROLS_ILLUMINATION, hbmProvider,
+ REQUEST_ID, reason, controllerCallback, onTouch, activityLaunchAnimator)
block()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java
index 27755edecba6..cd646c665d03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java
@@ -62,6 +62,7 @@ public class UdfpsDialogMeasureAdapterTest extends SysuiTestCase {
0 /* sensorId */, SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
componentInfo,
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
+ true /* halControlsIllumination */,
true /* resetLockoutRequiresHardwareAuthToken */,
List.of(new SensorLocationInternal("" /* displayId */,
sensorLocationX, sensorLocationY, sensorRadius)));
@@ -127,6 +128,7 @@ public class UdfpsDialogMeasureAdapterTest extends SysuiTestCase {
0 /* sensorId */, SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
componentInfo,
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
+ true /* halControlsIllumination */,
true /* resetLockoutRequiresHardwareAuthToken */,
List.of(new SensorLocationInternal("" /* displayId */,
sensorLocationX, sensorLocationY, sensorRadius)));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
index 744af589dfac..0327cfcf3450 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
@@ -36,6 +36,7 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.nullable
@@ -43,7 +44,6 @@ import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
import org.mockito.Mockito.`when` as whenever
-private const val DISPLAY_ID = "" // default display id
private const val SENSOR_X = 50
private const val SENSOR_Y = 250
private const val SENSOR_RADIUS = 10
@@ -146,7 +146,7 @@ class UdfpsViewTest : SysuiTestCase() {
view.startIllumination(onDone)
val illuminator = withArgCaptor<Runnable> {
- verify(hbmProvider).enableHbm(capture())
+ verify(hbmProvider).enableHbm(anyBoolean(), capture())
}
assertThat(view.isIlluminationRequested).isTrue()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index ae387e86de9d..4eeb4acf18b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -45,7 +45,6 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dock.DockManager;
import com.android.systemui.doze.DozeTriggers.DozingUpdateUiEvent;
import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
@@ -91,8 +90,6 @@ public class DozeTriggersTest extends SysuiTestCase {
private KeyguardStateController mKeyguardStateController;
@Mock
private DevicePostureController mDevicePostureController;
- @Mock
- private BatteryController mBatteryController;
private DozeTriggers mTriggers;
private FakeSensorManager mSensors;
@@ -125,7 +122,7 @@ public class DozeTriggersTest extends SysuiTestCase {
asyncSensorManager, wakeLock, mDockManager, mProximitySensor,
mProximityCheck, mock(DozeLog.class), mBroadcastDispatcher, new FakeSettings(),
mAuthController, mExecutor, mUiEventLogger, mKeyguardStateController,
- mDevicePostureController, mBatteryController);
+ mDevicePostureController);
mTriggers.setDozeMachine(mMachine);
waitForSensorManager();
}
@@ -233,9 +230,7 @@ public class DozeTriggersTest extends SysuiTestCase {
when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
// WHEN the pick up gesture is triggered and keyguard isn't occluded
- // and device isn't on a wireless charger
when(mKeyguardStateController.isOccluded()).thenReturn(false);
- when(mBatteryController.isPluggedInWireless()).thenReturn(false);
mTriggers.onSensor(DozeLog.REASON_SENSOR_PICKUP, 100, 100, null);
// THEN wakeup
@@ -249,22 +244,6 @@ public class DozeTriggersTest extends SysuiTestCase {
// WHEN the pick up gesture is triggered and keyguard IS occluded
when(mKeyguardStateController.isOccluded()).thenReturn(true);
- when(mBatteryController.isPluggedInWireless()).thenReturn(false);
- mTriggers.onSensor(DozeLog.REASON_SENSOR_PICKUP, 100, 100, null);
-
- // THEN never wakeup
- verify(mMachine, never()).wakeUp();
- }
-
- @Test
- public void testPickupGestureWirelessCharger() {
- // GIVEN device is in doze (screen blank, but running doze sensors)
- when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
-
- // WHEN the pick up gesture is triggered
- // and device IS on a wireless charger
- when(mKeyguardStateController.isOccluded()).thenReturn(false);
- when(mBatteryController.isPluggedInWireless()).thenReturn(true);
mTriggers.onSensor(DozeLog.REASON_SENSOR_PICKUP, 100, 100, null);
// THEN never wakeup
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
index 2d8c4d5dceb0..2abc666e87fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
@@ -14,6 +14,7 @@ import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardViewController
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.policy.KeyguardStateController
import junit.framework.Assert.assertEquals
@@ -49,6 +50,8 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() {
private lateinit var biometricUnlockController: BiometricUnlockController
@Mock
private lateinit var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier
+ @Mock
+ private lateinit var statusBarStateController: SysuiStatusBarStateController
private lateinit var remoteAnimationTarget: RemoteAnimationTarget
@@ -57,7 +60,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
keyguardUnlockAnimationController = KeyguardUnlockAnimationController(
context, keyguardStateController, { keyguardViewMediator }, keyguardViewController,
- featureFlags, { biometricUnlockController }
+ featureFlags, { biometricUnlockController }, statusBarStateController
)
`when`(keyguardViewController.viewRootImpl).thenReturn(mock(ViewRootImpl::class.java))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
index be923a68391c..c532ed5ab651 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
@@ -34,8 +34,6 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.AnimatedStateListDrawable;
import android.hardware.biometrics.BiometricSourceType;
-import android.hardware.biometrics.SensorLocationInternal;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.Pair;
@@ -77,9 +75,6 @@ import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
-import java.util.ArrayList;
-import java.util.List;
-
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@@ -182,7 +177,7 @@ public class LockIconViewControllerTest extends SysuiTestCase {
@Test
public void testUpdateFingerprintLocationOnInit() {
// GIVEN fp sensor location is available pre-attached
- Pair<Integer, PointF> udfps = setupUdfps(); // first = radius, second = udfps location
+ Pair<Float, PointF> udfps = setupUdfps(); // first = radius, second = udfps location
// WHEN lock icon view controller is initialized and attached
mLockIconViewController.init();
@@ -197,7 +192,7 @@ public class LockIconViewControllerTest extends SysuiTestCase {
@Test
public void testUpdatePaddingBasedOnResolutionScale() {
// GIVEN fp sensor location is available pre-attached & scaled resolution factor is 5
- Pair<Integer, PointF> udfps = setupUdfps(); // first = radius, second = udfps location
+ Pair<Float, PointF> udfps = setupUdfps(); // first = radius, second = udfps location
when(mAuthController.getScaleFactor()).thenReturn(5f);
// WHEN lock icon view controller is initialized and attached
@@ -215,20 +210,19 @@ public class LockIconViewControllerTest extends SysuiTestCase {
// GIVEN fp sensor location is not available pre-init
when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
when(mAuthController.getFingerprintSensorLocation()).thenReturn(null);
- when(mAuthController.getUdfpsProps()).thenReturn(null);
mLockIconViewController.init();
captureAttachListener();
mAttachListener.onViewAttachedToWindow(mLockIconView);
- // GIVEN fp sensor location is available post-atttached
+ // GIVEN fp sensor location is available post-attached
captureAuthControllerCallback();
- Pair<Integer, PointF> udfps = setupUdfps();
+ Pair<Float, PointF> udfps = setupUdfps();
// WHEN all authenticators are registered
mAuthControllerCallback.onAllAuthenticatorsRegistered();
mDelayableExecutor.runAllReady();
- // THEN lock icon view location is updated with the same coordinates as fpProps
+ // THEN lock icon view location is updated with the same coordinates as auth controller vals
verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first),
eq(PADDING));
}
@@ -402,20 +396,10 @@ public class LockIconViewControllerTest extends SysuiTestCase {
verify(mLockIconView).setTranslationX(0);
}
- private Pair<Integer, PointF> setupUdfps() {
+ private Pair<Float, PointF> setupUdfps() {
when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
final PointF udfpsLocation = new PointF(50, 75);
- final int radius = 33;
- final FingerprintSensorPropertiesInternal fpProps =
- new FingerprintSensorPropertiesInternal(
- /* sensorId */ 0,
- /* strength */ 0,
- /* max enrollments per user */ 5,
- /* component info */ new ArrayList<>(),
- /* sensorType */ 3,
- /* resetLockoutRequiresHwToken */ false,
- List.of(new SensorLocationInternal("" /* displayId */,
- (int) udfpsLocation.x, (int) udfpsLocation.y, radius)));
+ final float radius = 33f;
when(mAuthController.getUdfpsLocation()).thenReturn(udfpsLocation);
when(mAuthController.getUdfpsRadius()).thenReturn(radius);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index f9fb8657d0f6..0ed579f9c10b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -21,7 +21,6 @@ import android.animation.AnimatorSet
import android.app.PendingIntent
import android.app.smartspace.SmartspaceAction
import android.content.Context
-import org.mockito.Mockito.`when` as whenever
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
@@ -67,8 +66,8 @@ import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.KotlinArgumentCaptor
-import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.withArgCaptor
@@ -92,6 +91,7 @@ import org.mockito.Mockito.reset
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.`when` as whenever
private const val KEY = "TEST_KEY"
private const val PACKAGE = "PKG"
@@ -339,6 +339,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
whenever(viewHolder.player).thenReturn(view)
whenever(viewHolder.appIcon).thenReturn(appIcon)
whenever(viewHolder.albumView).thenReturn(albumView)
+ whenever(albumView.foreground).thenReturn(mock(Drawable::class.java))
whenever(viewHolder.titleText).thenReturn(titleText)
whenever(viewHolder.artistText).thenReturn(artistText)
whenever(seamlessBackground.getDrawable(0)).thenReturn(mock(GradientDrawable::class.java))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaViewControllerTest.kt
index 604e1f3ac9eb..18178097d5e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaViewControllerTest.kt
@@ -4,16 +4,22 @@ import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
import androidx.test.filters.SmallTest
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.animation.MeasurementInput
import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.animation.TransitionViewState
+import com.android.systemui.util.animation.WidgetState
import junit.framework.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
/**
* Tests for {@link MediaViewController}.
@@ -31,6 +37,9 @@ class MediaViewControllerTest : SysuiTestCase() {
private lateinit var mediaViewController: MediaViewController
private val mediaHostStateHolder = MediaHost.MediaHostStateHolder()
private var transitionLayout = TransitionLayout(context, /* attrs */ null, /* defStyleAttr */ 0)
+ @Mock private lateinit var mockViewState: TransitionViewState
+ @Mock private lateinit var mockCopiedState: TransitionViewState
+ @Mock private lateinit var mockWidgetState: WidgetState
@Before
fun setUp() {
@@ -63,4 +72,15 @@ class MediaViewControllerTest : SysuiTestCase() {
mediaHostStateHolder.squishFraction = 0.5f
assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.height == 50)
}
+
+ @Test
+ fun testSquish_DoesNotMutateViewState() {
+ whenever(mockViewState.copy()).thenReturn(mockCopiedState)
+ whenever(mockCopiedState.widgetStates)
+ .thenReturn(mutableMapOf(R.id.album_art to mockWidgetState))
+
+ mediaViewController.squishViewState(mockViewState, 0.5f)
+ verify(mockViewState, times(1)).copy()
+ verifyNoMoreInteractions(mockViewState)
+ }
} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt
index 06d45de699e3..a07447521ab8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt
@@ -27,6 +27,7 @@ import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -136,6 +137,24 @@ public class ResumeMediaBrowserTest : SysuiTestCase() {
}
@Test
+ fun testConnection_calledTwice_oldBrowserDisconnected() {
+ val oldBrowser = mock<MediaBrowser>()
+ whenever(browserFactory.create(any(), any(), any())).thenReturn(oldBrowser)
+
+ // When testConnection can connect to the service
+ setupBrowserConnection()
+ resumeBrowser.testConnection()
+
+ // And testConnection is called again
+ val newBrowser = mock<MediaBrowser>()
+ whenever(browserFactory.create(any(), any(), any())).thenReturn(newBrowser)
+ resumeBrowser.testConnection()
+
+ // Then we disconnect the old browser
+ verify(oldBrowser).disconnect()
+ }
+
+ @Test
fun testFindRecentMedia_connectionFails_error() {
// When findRecentMedia is called and we cannot connect
setupBrowserFailed()
@@ -169,6 +188,24 @@ public class ResumeMediaBrowserTest : SysuiTestCase() {
}
@Test
+ fun testFindRecentMedia_calledTwice_oldBrowserDisconnected() {
+ val oldBrowser = mock<MediaBrowser>()
+ whenever(browserFactory.create(any(), any(), any())).thenReturn(oldBrowser)
+
+ // When findRecentMedia is called and we connect
+ setupBrowserConnection()
+ resumeBrowser.findRecentMedia()
+
+ // And findRecentMedia is called again
+ val newBrowser = mock<MediaBrowser>()
+ whenever(browserFactory.create(any(), any(), any())).thenReturn(newBrowser)
+ resumeBrowser.findRecentMedia()
+
+ // Then we disconnect the old browser
+ verify(oldBrowser).disconnect()
+ }
+
+ @Test
fun testFindRecentMedia_noChildren_error() {
// When findRecentMedia is called and we connect, but do not get any results
setupBrowserConnectionNoResults()
@@ -223,6 +260,24 @@ public class ResumeMediaBrowserTest : SysuiTestCase() {
verify(transportControls).play()
}
+ @Test
+ fun testRestart_calledTwice_oldBrowserDisconnected() {
+ val oldBrowser = mock<MediaBrowser>()
+ whenever(browserFactory.create(any(), any(), any())).thenReturn(oldBrowser)
+
+ // When restart is called and we connect successfully
+ setupBrowserConnection()
+ resumeBrowser.restart()
+
+ // And restart is called again
+ val newBrowser = mock<MediaBrowser>()
+ whenever(browserFactory.create(any(), any(), any())).thenReturn(newBrowser)
+ resumeBrowser.restart()
+
+ // Then we disconnect the old browser
+ verify(oldBrowser).disconnect()
+ }
+
/**
* Helper function to mock a failed connection
*/
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 3c6aa4803e86..a52608733ca4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -60,6 +60,7 @@ import android.view.Display;
import android.view.DisplayInfo;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowMetrics;
@@ -85,6 +86,7 @@ import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.recents.Recents;
+import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.rotation.RotationButtonController;
import com.android.systemui.statusbar.CommandQueue;
@@ -163,7 +165,7 @@ public class NavigationBarTest extends SysuiTestCase {
@Mock
private UiEventLogger mUiEventLogger;
@Mock
- EdgeBackGestureHandler.Factory mEdgeBackGestureHandlerFactory;
+ private ViewTreeObserver mViewTreeObserver;
@Mock
EdgeBackGestureHandler mEdgeBackGestureHandler;
NavBarHelper mNavBarHelper;
@@ -187,6 +189,8 @@ public class NavigationBarTest extends SysuiTestCase {
private DeadZone mDeadZone;
@Mock
private CentralSurfaces mCentralSurfaces;
+ @Mock
+ private UserContextProvider mUserContextProvider;
private DeviceConfigProxyFake mDeviceConfigProxyFake = new DeviceConfigProxyFake();
@Rule
@@ -196,8 +200,6 @@ public class NavigationBarTest extends SysuiTestCase {
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
- when(mEdgeBackGestureHandlerFactory.create(any(Context.class)))
- .thenReturn(mEdgeBackGestureHandler);
when(mLightBarcontrollerFactory.create(any(Context.class))).thenReturn(mLightBarController);
when(mAutoHideControllerFactory.create(any(Context.class))).thenReturn(mAutoHideController);
when(mNavigationBarView.getHomeButton()).thenReturn(mHomeButton);
@@ -210,6 +212,9 @@ public class NavigationBarTest extends SysuiTestCase {
when(mNavigationBarTransitions.getLightTransitionsController())
.thenReturn(mLightBarTransitionsController);
when(mStatusBarKeyguardViewManager.isNavBarVisible()).thenReturn(true);
+ when(mNavigationBarView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
+ when(mUserContextProvider.createCurrentUserContext(any(Context.class)))
+ .thenReturn(mContext);
setupSysuiDependency();
// This class inflates views that call Dependency.get, thus these injections are still
// necessary.
@@ -217,8 +222,6 @@ public class NavigationBarTest extends SysuiTestCase {
mDependency.injectMockDependency(KeyguardStateController.class);
mDependency.injectTestDependency(StatusBarStateController.class, mStatusBarStateController);
mDependency.injectMockDependency(NavigationBarController.class);
- mDependency.injectTestDependency(EdgeBackGestureHandler.Factory.class,
- mEdgeBackGestureHandlerFactory);
mDependency.injectTestDependency(OverviewProxyService.class, mOverviewProxyService);
mDependency.injectTestDependency(NavigationModeController.class, mNavigationModeController);
TestableLooper.get(this).runWithLooper(() -> {
@@ -458,7 +461,9 @@ public class NavigationBarTest extends SysuiTestCase {
mDeadZone,
mDeviceConfigProxyFake,
mNavigationBarTransitions,
- Optional.of(mock(BackAnimation.class))));
+ mEdgeBackGestureHandler,
+ Optional.of(mock(BackAnimation.class)),
+ mUserContextProvider));
}
private void processAllMessages() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
index c88ceac458ee..4f6475f0148b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
@@ -90,6 +90,7 @@ public class QuickAccessWalletTileTest extends SysuiTestCase {
private static final String CARD_ID = "card_id";
private static final String LABEL = "QAW";
+ private static final String CARD_DESCRIPTION = "•••• 1234";
private static final Icon CARD_IMAGE =
Icon.createWithBitmap(Bitmap.createBitmap(70, 50, Bitmap.Config.ARGB_8888));
@@ -282,9 +283,7 @@ public class QuickAccessWalletTileTest extends SysuiTestCase {
mTile.handleUpdateState(state, null);
assertEquals(Tile.STATE_ACTIVE, state.state);
- assertEquals(
- "•••• 1234",
- state.secondaryLabel);
+ assertEquals(CARD_DESCRIPTION, state.secondaryLabel);
assertNotNull(state.stateDescription);
assertNotNull(state.sideViewCustomDrawable);
}
@@ -298,11 +297,9 @@ public class QuickAccessWalletTileTest extends SysuiTestCase {
mTile.handleUpdateState(state, null);
assertEquals(Tile.STATE_INACTIVE, state.state);
- assertEquals(
- mContext.getString(R.string.wallet_secondary_label_device_locked),
- state.secondaryLabel);
+ assertEquals(CARD_DESCRIPTION, state.secondaryLabel);
assertNotNull(state.stateDescription);
- assertNull(state.sideViewCustomDrawable);
+ assertNotNull(state.sideViewCustomDrawable);
}
@Test
@@ -314,9 +311,7 @@ public class QuickAccessWalletTileTest extends SysuiTestCase {
mTile.handleUpdateState(state, null);
assertEquals(Tile.STATE_ACTIVE, state.state);
- assertEquals(
- "•••• 1234",
- state.secondaryLabel);
+ assertEquals(CARD_DESCRIPTION, state.secondaryLabel);
assertNotNull(state.stateDescription);
assertNotNull(state.sideViewCustomDrawable);
}
@@ -426,6 +421,6 @@ public class QuickAccessWalletTileTest extends SysuiTestCase {
private WalletCard createWalletCard(Context context) {
PendingIntent pendingIntent =
PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE);
- return new WalletCard.Builder(CARD_ID, CARD_IMAGE, "•••• 1234", pendingIntent).build();
+ return new WalletCard.Builder(CARD_ID, CARD_IMAGE, CARD_DESCRIPTION, pendingIntent).build();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt
index 540d2918319f..1cce3b5b9e77 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt
@@ -21,10 +21,13 @@ import android.provider.Settings
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS_USE_PEOPLE_FILTERING
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.DeviceConfigProxyFake
+import com.android.systemui.util.Utils
+import com.android.systemui.util.mockito.any
import org.junit.After
import org.junit.Assert.assertFalse
@@ -32,28 +35,33 @@ import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoSession
+import org.mockito.quality.Strictness
@RunWith(AndroidTestingRunner::class)
@SmallTest
class NotificationSectionsFeatureManagerTest : SysuiTestCase() {
var manager: NotificationSectionsFeatureManager? = null
val proxyFake = DeviceConfigProxyFake()
- var originalQsMediaPlayer: Int = 0
+ private lateinit var staticMockSession: MockitoSession
@Before
public fun setup() {
manager = NotificationSectionsFeatureManager(proxyFake, mContext)
manager!!.clearCache()
- originalQsMediaPlayer = Settings.Global.getInt(context.getContentResolver(),
- Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1)
+ staticMockSession = ExtendedMockito.mockitoSession()
+ .mockStatic<Utils>(Utils::class.java)
+ .strictness(Strictness.LENIENT)
+ .startMocking()
+ `when`(Utils.useQsMediaPlayer(any())).thenReturn(false)
Settings.Global.putInt(context.getContentResolver(),
Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 0)
}
@After
public fun teardown() {
- Settings.Global.putInt(context.getContentResolver(),
- Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, originalQsMediaPlayer)
+ staticMockSession.finishMocking()
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
index b45d78d5502d..4b458f5a9123 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
@@ -88,4 +88,7 @@ public class GroupEntryBuilder {
return this;
}
+ public static List<NotificationEntry> getRawChildren(GroupEntry groupEntry) {
+ return groupEntry.getRawChildren();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
index 7692a05eb5fc..742fcf5e03c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
@@ -21,17 +21,22 @@ import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.GroupEntry
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
import com.android.systemui.statusbar.notification.collection.render.NodeController
+import com.android.systemui.statusbar.notification.icon.ConversationIconManager
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertFalse
@@ -52,8 +57,10 @@ class ConversationCoordinatorTest : SysuiTestCase() {
private lateinit var promoter: NotifPromoter
private lateinit var peopleSectioner: NotifSectioner
private lateinit var peopleComparator: NotifComparator
+ private lateinit var beforeRenderListListener: OnBeforeRenderListListener
@Mock private lateinit var pipeline: NotifPipeline
+ @Mock private lateinit var conversationIconManager: ConversationIconManager
@Mock private lateinit var peopleNotificationIdentifier: PeopleNotificationIdentifier
@Mock private lateinit var channel: NotificationChannel
@Mock private lateinit var headerController: NodeController
@@ -66,7 +73,11 @@ class ConversationCoordinatorTest : SysuiTestCase() {
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- coordinator = ConversationCoordinator(peopleNotificationIdentifier, headerController)
+ coordinator = ConversationCoordinator(
+ peopleNotificationIdentifier,
+ conversationIconManager,
+ headerController
+ )
whenever(channel.isImportantConversation).thenReturn(true)
coordinator.attach(pipeline)
@@ -75,6 +86,9 @@ class ConversationCoordinatorTest : SysuiTestCase() {
promoter = withArgCaptor {
verify(pipeline).addPromoter(capture())
}
+ beforeRenderListListener = withArgCaptor {
+ verify(pipeline).addOnBeforeRenderListListener(capture())
+ }
peopleSectioner = coordinator.sectioner
peopleComparator = peopleSectioner.comparator!!
@@ -96,6 +110,25 @@ class ConversationCoordinatorTest : SysuiTestCase() {
}
@Test
+ fun testPromotedImportantConversationsMakesSummaryUnimportant() {
+ val altChildA = NotificationEntryBuilder().setTag("A").build()
+ val altChildB = NotificationEntryBuilder().setTag("B").build()
+ val summary = NotificationEntryBuilder().setId(2).setChannel(channel).build()
+ val groupEntry = GroupEntryBuilder()
+ .setParent(GroupEntry.ROOT_ENTRY)
+ .setSummary(summary)
+ .setChildren(listOf(entry, altChildA, altChildB))
+ .build()
+ assertTrue(promoter.shouldPromoteToTopLevel(entry))
+ assertFalse(promoter.shouldPromoteToTopLevel(altChildA))
+ assertFalse(promoter.shouldPromoteToTopLevel(altChildB))
+ NotificationEntryBuilder.setNewParent(entry, GroupEntry.ROOT_ENTRY)
+ GroupEntryBuilder.getRawChildren(groupEntry).remove(entry)
+ beforeRenderListListener.onBeforeRenderList(listOf(entry, groupEntry))
+ verify(conversationIconManager).setUnimportantConversations(eq(listOf(summary.key)))
+ }
+
+ @Test
fun testInPeopleSection() {
whenever(peopleNotificationIdentifier.getPeopleNotificationType(entry))
.thenReturn(TYPE_PERSON)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
index cf996073f6a0..3a85972aa470 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
@@ -22,11 +22,14 @@ import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_MIN;
+import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
+import static com.android.systemui.statusbar.StatusBarState.SHADE;
import static com.android.systemui.statusbar.notification.collection.EntryUtilKt.modifyEntry;
import static com.android.systemui.util.mockito.KotlinMockitoHelpersKt.argThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
@@ -55,6 +58,7 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.RankingBuilder;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -90,7 +94,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase {
@Mock private NotificationLockscreenUserManager mLockscreenUserManager;
@Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock private HighPriorityProvider mHighPriorityProvider;
- @Mock private StatusBarStateController mStatusBarStateController;
+ @Mock private SysuiStatusBarStateController mStatusBarStateController;
@Mock private BroadcastDispatcher mBroadcastDispatcher;
private final FakeSettings mFakeSettings = new FakeSettings();
@@ -178,7 +182,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase {
Consumer<String> listener = mock(Consumer.class);
mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener);
- callback.onStateChanged(0);
+ callback.onUpcomingStateChanged(0);
verify(listener).accept(anyString());
}
@@ -199,7 +203,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase {
Consumer<String> listener = mock(Consumer.class);
mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener);
- when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
callback.onReceive(mContext, new Intent(Intent.ACTION_USER_SWITCHED));
verify(listener).accept(anyString());
@@ -207,7 +211,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase {
@Test
public void notifyListeners_onSettingChange_lockScreenShowNotifs() {
- when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
Consumer<String> listener = mock(Consumer.class);
mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener);
@@ -218,7 +222,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase {
@Test
public void notifyListeners_onSettingChange_lockScreenAllowPrivateNotifs() {
- when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
Consumer<String> listener = mock(Consumer.class);
mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener);
@@ -228,8 +232,43 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase {
}
@Test
+ public void hideSilentNotificationsPerUserSettingWithHighPriorityParent() {
+ when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
+ mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
+ mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
+ GroupEntry parent = new GroupEntryBuilder()
+ .setKey("parent")
+ .addChild(mEntry)
+ .setSummary(new NotificationEntryBuilder()
+ .setUser(new UserHandle(NOTIF_USER_ID))
+ .setImportance(IMPORTANCE_LOW)
+ .build())
+ .build();
+ mEntry = new NotificationEntryBuilder()
+ .setUser(new UserHandle(NOTIF_USER_ID))
+ .setImportance(IMPORTANCE_LOW)
+ .setParent(parent)
+ .build();
+ when(mHighPriorityProvider.isHighPriority(any())).thenReturn(false);
+ assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+ }
+
+ @Test
+ public void hideSilentNotificationsPerUserSetting() {
+ when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
+ mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true);
+ mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
+ mEntry = new NotificationEntryBuilder()
+ .setUser(new UserHandle(NOTIF_USER_ID))
+ .setImportance(IMPORTANCE_LOW)
+ .build();
+ when(mHighPriorityProvider.isHighPriority(any())).thenReturn(false);
+ assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+ }
+
+ @Test
public void notifyListeners_onSettingChange_zenMode() {
- when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
Consumer<String> listener = mock(Consumer.class);
mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener);
@@ -240,7 +279,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase {
@Test
public void notifyListeners_onSettingChange_lockScreenShowSilentNotifs() {
- when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
Consumer<String> listener = mock(Consumer.class);
mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener);
@@ -262,7 +301,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase {
public void keyguardNotShowing() {
// GIVEN the lockscreen isn't showing
setupUnfilteredState(mEntry);
- when(mKeyguardStateController.isShowing()).thenReturn(false);
+ when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(SHADE);
// THEN don't filter out the entry
assertFalse(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
@@ -384,8 +423,8 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase {
mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false);
when(mHighPriorityProvider.isHighPriority(parent)).thenReturn(true);
- // THEN don't filter out the entry
- assertFalse(
+ // THEN filter out the entry regardless of parent
+ assertTrue(
mKeyguardNotificationVisibilityProvider.shouldHideNotification(entryWithParent));
// WHEN its parent doesn't exceed threshold to show on lockscreen
@@ -404,7 +443,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase {
*/
private void setupUnfilteredState(NotificationEntry entry) {
// keyguard is showing
- when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
// show notifications on the lockscreen
when(mLockscreenUserManager.shouldShowLockscreenNotifications()).thenReturn(true);
@@ -452,11 +491,11 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase {
@BindsInstance NotificationLockscreenUserManager lockscreenUserManager,
@BindsInstance KeyguardUpdateMonitor keyguardUpdateMonitor,
@BindsInstance HighPriorityProvider highPriorityProvider,
- @BindsInstance StatusBarStateController statusBarStateController,
+ @BindsInstance SysuiStatusBarStateController statusBarStateController,
@BindsInstance BroadcastDispatcher broadcastDispatcher,
@BindsInstance SecureSettings secureSettings,
@BindsInstance GlobalSettings globalSettings
);
}
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
index 4986792373db..f43c2a183465 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
@@ -160,7 +160,7 @@ public class KeyguardBouncerTest extends SysuiTestCase {
@Test
public void testShow_notifiesVisibility() {
mBouncer.show(true);
- verify(mViewMediatorCallback).onBouncerVisiblityChanged(eq(true));
+ verify(mKeyguardStateController).notifyBouncerShowing(eq(true));
verify(mExpansionCallback).onStartingToShow();
// Not called again when visible
@@ -238,7 +238,7 @@ public class KeyguardBouncerTest extends SysuiTestCase {
@Test
public void testHide_notifiesVisibility() {
mBouncer.hide(false);
- verify(mViewMediatorCallback).onBouncerVisiblityChanged(eq(false));
+ verify(mKeyguardStateController).notifyBouncerShowing(eq(false));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index 6d3a5fecc826..ec20271ea43b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -336,7 +336,7 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
// WHEN the position algorithm is run
positionClock();
// THEN the padding DOESN'T adjust for keyguard status height.
- assertThat(mClockPositionAlgorithm.getMinStackScrollerPadding())
+ assertThat(mClockPositionAlgorithm.getLockscreenMinStackScrollerPadding())
.isEqualTo(mKeyguardStatusBarHeaderHeight);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
index 94e6b9a12a93..11f96cec6369 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
@@ -610,13 +610,13 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
when(mLockIconViewController.getTop()).thenReturn(80f);
when(mResources.getDimensionPixelSize(R.dimen.shelf_and_lock_icon_overlap)).thenReturn(5);
- // Available space (100 - 10 - 15 = 75)
+ // Available space (100 - 0 - 15 = 85)
when(mNotificationStackScrollLayoutController.getHeight()).thenReturn(100);
- when(mNotificationStackScrollLayoutController.getTopPadding()).thenReturn(10);
+ when(mNotificationStackScrollLayoutController.getTop()).thenReturn(0);
mNotificationPanelViewController.updateResources();
assertThat(mNotificationPanelViewController.getSpaceForLockscreenNotifications())
- .isEqualTo(75);
+ .isEqualTo(85);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
index ddccd834f76e..c402d2e47cf3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
@@ -26,10 +26,12 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.IActivityManager;
@@ -103,6 +105,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView);
mNotificationShadeWindowController.attach();
+ verify(mWindowManager).addView(eq(mNotificationShadeWindowView), any());
}
@Test
@@ -174,6 +177,14 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
}
@Test
+ public void setScrimsVisibility_earlyReturn() {
+ clearInvocations(mWindowManager);
+ mNotificationShadeWindowController.setScrimsVisibility(ScrimController.TRANSPARENT);
+ // Abort early if value didn't change
+ verify(mWindowManager, never()).updateViewLayout(any(), mLayoutParameters.capture());
+ }
+
+ @Test
public void attach_animatingKeyguardAndSurface_wallpaperVisible() {
clearInvocations(mWindowManager);
when(mKeyguardViewMediator.isShowingAndNotOccluded()).thenReturn(true);
@@ -221,6 +232,8 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
public void setPanelExpanded_notFocusable_altFocusable_whenPanelIsOpen() {
mNotificationShadeWindowController.setPanelExpanded(true);
clearInvocations(mWindowManager);
+ mNotificationShadeWindowController.setPanelExpanded(true);
+ verifyNoMoreInteractions(mWindowManager);
mNotificationShadeWindowController.setNotificationShadeFocusable(true);
verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
@@ -287,6 +300,8 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
public void batchApplyWindowLayoutParams_doesNotDispatchEvents() {
mNotificationShadeWindowController.setForceDozeBrightness(true);
verify(mWindowManager).updateViewLayout(any(), any());
+ mNotificationShadeWindowController.setForceDozeBrightness(true);
+ verifyNoMoreInteractions(mWindowManager);
clearInvocations(mWindowManager);
mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> {
@@ -295,4 +310,17 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
});
verify(mWindowManager).updateViewLayout(any(), any());
}
+
+ @Test
+ public void bouncerShowing_OrientationNoSensor() {
+ mNotificationShadeWindowController.setKeyguardShowing(true);
+ mNotificationShadeWindowController.setKeyguardOccluded(true);
+ mNotificationShadeWindowController.setBouncerShowing(true);
+ when(mKeyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(false);
+ mNotificationShadeWindowController.onConfigChanged(new Configuration());
+
+ verify(mWindowManager, atLeastOnce()).updateViewLayout(any(), mLayoutParameters.capture());
+ assertThat(mLayoutParameters.getValue().screenOrientation)
+ .isEqualTo(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
index 0936b773d4b3..011279721fd2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
@@ -117,11 +117,18 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() {
val keyguardSpy = spy(keyguardView)
Mockito.`when`(keyguardSpy.animate()).thenReturn(animator)
val listener = ArgumentCaptor.forClass(Animator.AnimatorListener::class.java)
+ val endAction = ArgumentCaptor.forClass(Runnable::class.java)
controller.animateInKeyguard(keyguardSpy, Runnable {})
Mockito.verify(animator).setListener(listener.capture())
- // Verify that the listener is cleared when it ends
- listener.value.onAnimationEnd(null)
+ Mockito.verify(animator).withEndAction(endAction.capture())
+
+ // Verify that the listener is cleared if we cancel it.
+ listener.value.onAnimationCancel(null)
Mockito.verify(animator).setListener(null)
+
+ // Verify that the listener is also cleared if the end action is triggered.
+ endAction.value.run()
+ verify(animator, times(2)).setListener(null)
}
/**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt
index e9d16a6738ca..64a93cf2c8dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt
@@ -53,7 +53,6 @@ import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
import org.mockito.Mockito.never
-import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.ArgumentMatchers.anyInt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java
index aaea4ecdc08d..95b62a12c621 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java
@@ -49,6 +49,11 @@ public class FakeKeyguardStateController implements KeyguardStateController {
}
@Override
+ public boolean isBouncerShowing() {
+ return false;
+ }
+
+ @Override
public boolean canDismissLockScreen() {
return false;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 193879e5c55c..238a4d37a872 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -57,6 +57,7 @@ import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
@@ -70,6 +71,8 @@ import android.service.notification.NotificationListenerService;
import android.service.notification.ZenModeConfig;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.util.Pair;
+import android.util.SparseArray;
import android.view.View;
import android.view.WindowManager;
@@ -147,6 +150,7 @@ import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.HashMap;
import java.util.List;
import java.util.Optional;
@@ -1010,7 +1014,7 @@ public class BubblesTest extends SysuiTestCase {
assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
// Should notify delegate that shade state changed
- verify(mBubbleController).onBubbleNotificationSuppressionChanged(
+ verify(mBubbleController).onBubbleMetadataFlagChanged(
mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
}
@@ -1027,7 +1031,7 @@ public class BubblesTest extends SysuiTestCase {
assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
// Should notify delegate that shade state changed
- verify(mBubbleController).onBubbleNotificationSuppressionChanged(
+ verify(mBubbleController).onBubbleMetadataFlagChanged(
mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
}
@@ -1447,6 +1451,69 @@ public class BubblesTest extends SysuiTestCase {
assertThat(stackView.getVisibility()).isEqualTo(View.VISIBLE);
}
+ @Test
+ public void testSetShouldAutoExpand_notifiesFlagChanged() {
+ mEntryListener.onPendingEntryAdded(mRow);
+
+ assertTrue(mBubbleController.hasBubbles());
+ Bubble b = mBubbleData.getBubbleInStackWithKey(mBubbleEntry.getKey());
+ assertThat(b.shouldAutoExpand()).isFalse();
+
+ // Set it to the same thing
+ b.setShouldAutoExpand(false);
+
+ // Verify it doesn't notify
+ verify(mBubbleController, never()).onBubbleMetadataFlagChanged(any());
+
+ // Set it to something different
+ b.setShouldAutoExpand(true);
+ verify(mBubbleController).onBubbleMetadataFlagChanged(b);
+ }
+
+ @Test
+ public void testUpdateBubble_skipsDndSuppressListNotifs() {
+ mBubbleEntry = new BubbleEntry(mRow.getSbn(), mRow.getRanking(), mRow.isDismissable(),
+ mRow.shouldSuppressNotificationDot(), true /* DndSuppressNotifFromList */,
+ mRow.shouldSuppressPeek());
+ mBubbleEntry.getBubbleMetadata().setFlags(
+ Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
+
+ mBubbleController.updateBubble(mBubbleEntry);
+
+ Bubble b = mBubbleData.getPendingBubbleWithKey(mBubbleEntry.getKey());
+ assertThat(b.shouldAutoExpand()).isFalse();
+ assertThat(mBubbleData.getBubbleInStackWithKey(mBubbleEntry.getKey())).isNull();
+ }
+
+ @Test
+ public void testOnRankingUpdate_DndSuppressListNotif() {
+ // It's in the stack
+ mBubbleController.updateBubble(mBubbleEntry);
+ assertThat(mBubbleData.hasBubbleInStackWithKey(mBubbleEntry.getKey())).isTrue();
+
+ // Set current user profile
+ SparseArray<UserInfo> userInfos = new SparseArray<>();
+ userInfos.put(mBubbleEntry.getStatusBarNotification().getUser().getIdentifier(),
+ mock(UserInfo.class));
+ mBubbleController.onCurrentProfilesChanged(userInfos);
+
+ // Send ranking update that the notif is suppressed from the list.
+ HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey = new HashMap<>();
+ mBubbleEntry = new BubbleEntry(mRow.getSbn(), mRow.getRanking(), mRow.isDismissable(),
+ mRow.shouldSuppressNotificationDot(), true /* DndSuppressNotifFromList */,
+ mRow.shouldSuppressPeek());
+ Pair<BubbleEntry, Boolean> pair = new Pair(mBubbleEntry, true);
+ entryDataByKey.put(mBubbleEntry.getKey(), pair);
+
+ NotificationListenerService.RankingMap rankingMap =
+ mock(NotificationListenerService.RankingMap.class);
+ when(rankingMap.getOrderedKeys()).thenReturn(new String[] { mBubbleEntry.getKey() });
+ mBubbleController.onRankingUpdated(rankingMap, entryDataByKey);
+
+ // Should no longer be in the stack
+ assertThat(mBubbleData.hasBubbleInStackWithKey(mBubbleEntry.getKey())).isFalse();
+ }
+
/** Creates a bubble using the userId and package. */
private Bubble createBubble(int userId, String pkg) {
final UserHandle userHandle = new UserHandle(userId);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
index 02d869172030..dff89e0a5558 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
@@ -50,6 +50,7 @@ import android.content.BroadcastReceiver;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.LauncherApps;
+import android.content.pm.UserInfo;
import android.hardware.display.AmbientDisplayConfiguration;
import android.os.Handler;
import android.os.PowerManager;
@@ -59,6 +60,8 @@ import android.service.notification.NotificationListenerService;
import android.service.notification.ZenModeConfig;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.util.Pair;
+import android.util.SparseArray;
import android.view.View;
import android.view.WindowManager;
@@ -128,6 +131,7 @@ import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.HashMap;
import java.util.List;
import java.util.Optional;
@@ -880,7 +884,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase {
assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
// Should notify delegate that shade state changed
- verify(mBubbleController).onBubbleNotificationSuppressionChanged(
+ verify(mBubbleController).onBubbleMetadataFlagChanged(
mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
}
@@ -897,7 +901,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase {
assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
// Should notify delegate that shade state changed
- verify(mBubbleController).onBubbleNotificationSuppressionChanged(
+ verify(mBubbleController).onBubbleMetadataFlagChanged(
mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
}
@@ -1267,6 +1271,69 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase {
assertThat(stackView.getVisibility()).isEqualTo(View.VISIBLE);
}
+ @Test
+ public void testSetShouldAutoExpand_notifiesFlagChanged() {
+ mBubbleController.updateBubble(mBubbleEntry);
+
+ assertTrue(mBubbleController.hasBubbles());
+ Bubble b = mBubbleData.getBubbleInStackWithKey(mBubbleEntry.getKey());
+ assertThat(b.shouldAutoExpand()).isFalse();
+
+ // Set it to the same thing
+ b.setShouldAutoExpand(false);
+
+ // Verify it doesn't notify
+ verify(mBubbleController, never()).onBubbleMetadataFlagChanged(any());
+
+ // Set it to something different
+ b.setShouldAutoExpand(true);
+ verify(mBubbleController).onBubbleMetadataFlagChanged(b);
+ }
+
+ @Test
+ public void testUpdateBubble_skipsDndSuppressListNotifs() {
+ mBubbleEntry = new BubbleEntry(mRow.getSbn(), mRow.getRanking(), mRow.isDismissable(),
+ mRow.shouldSuppressNotificationDot(), true /* DndSuppressNotifFromList */,
+ mRow.shouldSuppressPeek());
+ mBubbleEntry.getBubbleMetadata().setFlags(
+ Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
+
+ mBubbleController.updateBubble(mBubbleEntry);
+
+ Bubble b = mBubbleData.getPendingBubbleWithKey(mBubbleEntry.getKey());
+ assertThat(b.shouldAutoExpand()).isFalse();
+ assertThat(mBubbleData.getBubbleInStackWithKey(mBubbleEntry.getKey())).isNull();
+ }
+
+ @Test
+ public void testOnRankingUpdate_DndSuppressListNotif() {
+ // It's in the stack
+ mBubbleController.updateBubble(mBubbleEntry);
+ assertThat(mBubbleData.hasBubbleInStackWithKey(mBubbleEntry.getKey())).isTrue();
+
+ // Set current user profile
+ SparseArray<UserInfo> userInfos = new SparseArray<>();
+ userInfos.put(mBubbleEntry.getStatusBarNotification().getUser().getIdentifier(),
+ mock(UserInfo.class));
+ mBubbleController.onCurrentProfilesChanged(userInfos);
+
+ // Send ranking update that the notif is suppressed from the list.
+ HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey = new HashMap<>();
+ mBubbleEntry = new BubbleEntry(mRow.getSbn(), mRow.getRanking(), mRow.isDismissable(),
+ mRow.shouldSuppressNotificationDot(), true /* DndSuppressNotifFromList */,
+ mRow.shouldSuppressPeek());
+ Pair<BubbleEntry, Boolean> pair = new Pair(mBubbleEntry, true);
+ entryDataByKey.put(mBubbleEntry.getKey(), pair);
+
+ NotificationListenerService.RankingMap rankingMap =
+ mock(NotificationListenerService.RankingMap.class);
+ when(rankingMap.getOrderedKeys()).thenReturn(new String[] { mBubbleEntry.getKey() });
+ mBubbleController.onRankingUpdated(rankingMap, entryDataByKey);
+
+ // Should no longer be in the stack
+ assertThat(mBubbleData.hasBubbleInStackWithKey(mBubbleEntry.getKey())).isFalse();
+ }
+
/**
* Sets the bubble metadata flags for this entry. These flags are normally set by
* NotificationManagerService when the notification is sent, however, these tests do not
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 649328d1eef3..9b29bae1fc46 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -242,6 +242,9 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
int getCurrentUserIdLocked();
+ Pair<float[], MagnificationSpec> getWindowTransformationMatrixAndMagnificationSpec(
+ int windowId);
+
boolean isAccessibilityButtonShown();
/**
@@ -551,8 +554,6 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
final int resolvedWindowId;
RemoteAccessibilityConnection connection;
Region partialInteractiveRegion = Region.obtain();
- final MagnificationSpec spec;
- final float[] transformMatrix;
synchronized (mLock) {
mUsesAccessibilityCache = true;
if (!hasRightsToCurrentUserLocked()) {
@@ -576,11 +577,11 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
partialInteractiveRegion.recycle();
partialInteractiveRegion = null;
}
- final Pair<float[], MagnificationSpec> transformMatrixAndSpec =
- getTransformMatrixAndSpecLocked(resolvedWindowId);
- transformMatrix = transformMatrixAndSpec.first;
- spec = transformMatrixAndSpec.second;
}
+ final Pair<float[], MagnificationSpec> transformMatrixAndSpec =
+ getWindowTransformationMatrixAndMagnificationSpec(resolvedWindowId);
+ final float[] transformMatrix = transformMatrixAndSpec.first;
+ final MagnificationSpec spec = transformMatrixAndSpec.second;
if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
return null;
}
@@ -628,8 +629,6 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
final int resolvedWindowId;
RemoteAccessibilityConnection connection;
Region partialInteractiveRegion = Region.obtain();
- final MagnificationSpec spec;
- final float [] transformMatrix;
synchronized (mLock) {
mUsesAccessibilityCache = true;
if (!hasRightsToCurrentUserLocked()) {
@@ -653,11 +652,11 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
partialInteractiveRegion.recycle();
partialInteractiveRegion = null;
}
- final Pair<float[], MagnificationSpec> transformMatrixAndSpec =
- getTransformMatrixAndSpecLocked(resolvedWindowId);
- transformMatrix = transformMatrixAndSpec.first;
- spec = transformMatrixAndSpec.second;
}
+ final Pair<float[], MagnificationSpec> transformMatrixAndSpec =
+ getWindowTransformationMatrixAndMagnificationSpec(resolvedWindowId);
+ final float[] transformMatrix = transformMatrixAndSpec.first;
+ final MagnificationSpec spec = transformMatrixAndSpec.second;
if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
return null;
}
@@ -706,8 +705,6 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
final int resolvedWindowId;
RemoteAccessibilityConnection connection;
Region partialInteractiveRegion = Region.obtain();
- final MagnificationSpec spec;
- final float[] transformMatrix;
synchronized (mLock) {
mUsesAccessibilityCache = true;
if (!hasRightsToCurrentUserLocked()) {
@@ -731,11 +728,11 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
partialInteractiveRegion.recycle();
partialInteractiveRegion = null;
}
- final Pair<float[], MagnificationSpec> transformMatrixAndSpec =
- getTransformMatrixAndSpecLocked(resolvedWindowId);
- transformMatrix = transformMatrixAndSpec.first;
- spec = transformMatrixAndSpec.second;
}
+ final Pair<float[], MagnificationSpec> transformMatrixAndSpec =
+ getWindowTransformationMatrixAndMagnificationSpec(resolvedWindowId);
+ final float[] transformMatrix = transformMatrixAndSpec.first;
+ final MagnificationSpec spec = transformMatrixAndSpec.second;
if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
return null;
}
@@ -786,8 +783,6 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
final int resolvedWindowId;
RemoteAccessibilityConnection connection;
Region partialInteractiveRegion = Region.obtain();
- final MagnificationSpec spec;
- final float[] transformMatrix;
synchronized (mLock) {
if (!hasRightsToCurrentUserLocked()) {
return null;
@@ -811,11 +806,11 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
partialInteractiveRegion.recycle();
partialInteractiveRegion = null;
}
- final Pair<float[], MagnificationSpec> transformMatrixAndSpec =
- getTransformMatrixAndSpecLocked(resolvedWindowId);
- transformMatrix = transformMatrixAndSpec.first;
- spec = transformMatrixAndSpec.second;
}
+ final Pair<float[], MagnificationSpec> transformMatrixAndSpec =
+ getWindowTransformationMatrixAndMagnificationSpec(resolvedWindowId);
+ final float[] transformMatrix = transformMatrixAndSpec.first;
+ final MagnificationSpec spec = transformMatrixAndSpec.second;
if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
return null;
}
@@ -865,8 +860,6 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
final int resolvedWindowId;
RemoteAccessibilityConnection connection;
Region partialInteractiveRegion = Region.obtain();
- final MagnificationSpec spec;
- final float[] transformMatrix;
synchronized (mLock) {
if (!hasRightsToCurrentUserLocked()) {
return null;
@@ -889,12 +882,11 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
partialInteractiveRegion.recycle();
partialInteractiveRegion = null;
}
-
- final Pair<float[], MagnificationSpec> transformMatrixAndSpec =
- getTransformMatrixAndSpecLocked(resolvedWindowId);
- transformMatrix = transformMatrixAndSpec.first;
- spec = transformMatrixAndSpec.second;
}
+ final Pair<float[], MagnificationSpec> transformMatrixAndSpec =
+ getWindowTransformationMatrixAndMagnificationSpec(resolvedWindowId);
+ final float[] transformMatrix = transformMatrixAndSpec.first;
+ final MagnificationSpec spec = transformMatrixAndSpec.second;
if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
return null;
}
@@ -1672,21 +1664,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
mInvocationHandler.startInputLocked(connection, editorInfo, restarting);
}
-
-
@Nullable
- Pair<float[], MagnificationSpec> getTransformMatrixAndSpecLocked(int resolvedWindowId) {
- final WindowInfo windowInfo =
- mA11yWindowManager.findWindowInfoByIdLocked(resolvedWindowId);
- if (windowInfo == null) {
- Slog.w(LOG_TAG, "getTransformMatrixAndSpec, windowInfo is null window id = "
- + resolvedWindowId);
- return new Pair<>(null, null);
- }
-
- final MagnificationSpec spec = new MagnificationSpec();
- spec.setTo(windowInfo.mMagnificationSpec);
- return new Pair<>(windowInfo.mTransformMatrix, spec);
+ private Pair<float[], MagnificationSpec> getWindowTransformationMatrixAndMagnificationSpec(
+ int resolvedWindowId) {
+ return mSystemSupport.getWindowTransformationMatrixAndMagnificationSpec(resolvedWindowId);
}
/**
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 4806514a0e13..99c849527034 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -68,6 +68,7 @@ import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.database.ContentObserver;
+import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
@@ -99,6 +100,7 @@ import android.text.TextUtils;
import android.text.TextUtils.SimpleStringSplitter;
import android.util.ArraySet;
import android.util.IntArray;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
@@ -458,6 +460,41 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
@Override
+ public Pair<float[], MagnificationSpec> getWindowTransformationMatrixAndMagnificationSpec(
+ int windowId) {
+ WindowInfo windowInfo;
+ synchronized (mLock) {
+ windowInfo = mA11yWindowManager.findWindowInfoByIdLocked(windowId);
+ }
+ if (windowInfo != null) {
+ final MagnificationSpec spec = new MagnificationSpec();
+ spec.setTo(windowInfo.mMagnificationSpec);
+ return new Pair<>(windowInfo.mTransformMatrix, spec);
+ } else {
+ // If the framework doesn't track windows, we fall back to get the pair of
+ // transformation matrix and MagnificationSpe from the WindowManagerService's
+ // WindowState.
+ IBinder token;
+ synchronized (mLock) {
+ token = mA11yWindowManager.getWindowTokenForUserAndWindowIdLocked(mCurrentUserId,
+ windowId);
+ }
+ Pair<Matrix, MagnificationSpec> pair =
+ mWindowManagerService.getWindowTransformationMatrixAndMagnificationSpec(token);
+ final float[] outTransformationMatrix = new float[9];
+ final Matrix tmpMatrix = pair.first;
+ final MagnificationSpec spec = pair.second;
+ if (!spec.isNop()) {
+ tmpMatrix.postScale(spec.scale, spec.scale);
+ tmpMatrix.postTranslate(spec.offsetX, spec.offsetY);
+ }
+ tmpMatrix.getValues(outTransformationMatrix);
+
+ return new Pair<>(outTransformationMatrix, pair.second);
+ }
+ }
+
+ @Override
public void onServiceInfoChangedLocked(AccessibilityUserState userState) {
mSecurityPolicy.onBoundServicesChangedLocked(userState.mUserId,
userState.mBoundServices);
@@ -3750,12 +3787,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
boundsInScreenBeforeMagnification.centerY());
// Invert magnification if needed.
- final WindowInfo windowInfo = mA11yWindowManager.findWindowInfoByIdLocked(
- focus.getWindowId());
+ final Pair<float[], MagnificationSpec> pair =
+ getWindowTransformationMatrixAndMagnificationSpec(focus.getWindowId());
MagnificationSpec spec = null;
- if (windowInfo != null) {
+ if (pair != null && pair.second != null) {
spec = new MagnificationSpec();
- spec.setTo(windowInfo.mMagnificationSpec);
+ spec.setTo(pair.second);
}
if (spec != null && !spec.isNop()) {
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index f18d13d30da6..6d3620fa2ee6 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -1559,9 +1559,18 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
Slog.e(TAG, "Error sending input show up notification", e);
}
}
+ }
+
+ // AutoFillUiCallback
+ @Override
+ public void requestFallbackFromFillDialog() {
+ setFillDialogDisabled();
synchronized (mLock) {
- // stop to show fill dialog
- mSessionFlags.mFillDialogDisabled = true;
+ if (mCurrentViewId == null) {
+ return;
+ }
+ final ViewState currentView = mViewStates.get(mCurrentViewId);
+ currentView.maybeCallOnFillReady(mFlags);
}
}
@@ -3208,16 +3217,24 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return;
}
- if (requestShowFillDialog(response, filledId, filterText, flags)) {
- synchronized (mLock) {
- final ViewState currentView = mViewStates.get(mCurrentViewId);
- currentView.setState(ViewState.STATE_FILL_DIALOG_SHOWN);
- mService.logDatasetShown(id, mClientState, UI_TYPE_DIALOG);
+ final AutofillId[] ids = response.getFillDialogTriggerIds();
+ if (ids != null && ArrayUtils.contains(ids, filledId)) {
+ if (requestShowFillDialog(response, filledId, filterText, flags)) {
+ synchronized (mLock) {
+ final ViewState currentView = mViewStates.get(mCurrentViewId);
+ currentView.setState(ViewState.STATE_FILL_DIALOG_SHOWN);
+ mService.logDatasetShown(id, mClientState, UI_TYPE_DIALOG);
+ }
+ // Just show fill dialog once, so disabled after shown.
+ // Note: Cannot disable before requestShowFillDialog() because the method
+ // need to check whether fill dialog enabled.
+ setFillDialogDisabled();
+ return;
+ } else {
+ setFillDialogDisabled();
}
- return;
- }
- setFillDialogDisabled();
+ }
if (response.supportsInlineSuggestions()) {
synchronized (mLock) {
@@ -3324,15 +3341,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return false;
}
- final AutofillId[] ids = response.getFillDialogTriggerIds();
- if (ids == null || !ArrayUtils.contains(ids, filledId)) {
- return false;
- }
-
final Drawable serviceIcon = getServiceIcon();
getUiForShowing().showFillDialog(filledId, response, filterText,
- mService.getServicePackageName(), mComponentName, serviceIcon, this);
+ mService.getServicePackageName(), mComponentName, serviceIcon, this,
+ id, mCompatMode);
return true;
}
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 57768ef32391..3ab873de4bb5 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -97,6 +97,7 @@ public final class AutoFillUI {
void dispatchUnhandledKey(AutofillId id, KeyEvent keyEvent);
void cancelSession();
void requestShowSoftInput(AutofillId id);
+ void requestFallbackFromFillDialog();
}
public AutoFillUI(@NonNull Context context) {
@@ -388,13 +389,19 @@ public final class AutoFillUI {
public void showFillDialog(@NonNull AutofillId focusedId, @NonNull FillResponse response,
@Nullable String filterText, @Nullable String servicePackageName,
@NonNull ComponentName componentName, @Nullable Drawable serviceIcon,
- @NonNull AutoFillUiCallback callback) {
+ @NonNull AutoFillUiCallback callback, int sessionId, boolean compatMode) {
if (sVerbose) {
Slog.v(TAG, "showFillDialog for "
+ componentName.toShortString() + ": " + response);
}
- // TODO: enable LogMaker
+ final LogMaker log = Helper
+ .newLogMaker(MetricsEvent.AUTOFILL_FILL_UI, componentName, servicePackageName,
+ sessionId, compatMode)
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FILTERTEXT_LEN,
+ filterText == null ? 0 : filterText.length())
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS,
+ response.getDatasets() == null ? 0 : response.getDatasets().size());
mHandler.post(() -> {
if (callback != mCallback) {
@@ -406,6 +413,7 @@ public final class AutoFillUI {
mUiModeMgr.isNightMode(), new DialogFillUi.UiCallback() {
@Override
public void onResponsePicked(FillResponse response) {
+ log(MetricsEvent.TYPE_DETAIL);
hideFillDialogUiThread(callback);
if (mCallback != null) {
mCallback.authenticate(response.getRequestId(),
@@ -417,6 +425,7 @@ public final class AutoFillUI {
@Override
public void onDatasetPicked(Dataset dataset) {
+ log(MetricsEvent.TYPE_ACTION);
hideFillDialogUiThread(callback);
if (mCallback != null) {
final int datasetIndex = response.getDatasets().indexOf(dataset);
@@ -426,15 +435,29 @@ public final class AutoFillUI {
}
@Override
+ public void onDismissed() {
+ log(MetricsEvent.TYPE_DISMISS);
+ hideFillDialogUiThread(callback);
+ callback.requestShowSoftInput(focusedId);
+ }
+
+ @Override
public void onCanceled() {
+ log(MetricsEvent.TYPE_CLOSE);
hideFillDialogUiThread(callback);
callback.requestShowSoftInput(focusedId);
+ callback.requestFallbackFromFillDialog();
}
@Override
public void startIntentSender(IntentSender intentSender) {
mCallback.startIntentSenderAndFinishSession(intentSender);
}
+
+ private void log(int type) {
+ log.setType(type);
+ mMetricsLogger.write(log);
+ }
});
});
}
diff --git a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
index f9f5289ec6bf..5a1a1ae3908d 100644
--- a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
@@ -81,6 +81,7 @@ final class DialogFillUi {
interface UiCallback {
void onResponsePicked(@NonNull FillResponse response);
void onDatasetPicked(@NonNull Dataset dataset);
+ void onDismissed();
void onCanceled();
void startIntentSender(IntentSender intentSender);
}
@@ -144,6 +145,7 @@ final class DialogFillUi {
mDialog = new Dialog(mContext, mThemeId);
mDialog.setContentView(decor);
setDialogParamsAsBottomSheet();
+ mDialog.setOnCancelListener((d) -> mCallback.onCanceled());
show();
}
@@ -220,7 +222,7 @@ final class DialogFillUi {
final TextView noButton = decor.findViewById(R.id.autofill_dialog_no);
// set "No thinks" by default
noButton.setText(R.string.autofill_save_no);
- noButton.setOnClickListener((v) -> mCallback.onCanceled());
+ noButton.setOnClickListener((v) -> mCallback.onDismissed());
}
private void setContinueButton(View decor, View.OnClickListener listener) {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index b4c107c1a2c1..3b11038daf7e 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -294,6 +294,15 @@ public class CompanionDeviceManagerService extends SystemService {
private boolean onCompanionApplicationBindingDiedInternal(
@UserIdInt int userId, @NonNull String packageName) {
+ // Update the current connected devices sets when binderDied, so that application is able
+ // to call notifyDeviceAppeared after re-launch the application.
+ for (AssociationInfo ai :
+ mAssociationStore.getAssociationsForPackage(userId, packageName)) {
+ int id = ai.getId();
+ Slog.i(TAG, "Removing association id: " + id + " for package: "
+ + packageName + " due to binderDied.");
+ mDevicePresenceMonitor.removeDeviceFromMonitoring(id);
+ }
// TODO(b/218613015): implement.
return false;
}
@@ -723,9 +732,12 @@ public class CompanionDeviceManagerService extends SystemService {
String[] args, ShellCallback callback, ResultReceiver resultReceiver)
throws RemoteException {
enforceCallerCanManageCompanionDevice(getContext(), "onShellCommand");
- new CompanionDeviceShellCommand(
- CompanionDeviceManagerService.this, mAssociationStore)
- .exec(this, in, out, err, args, callback, resultReceiver);
+
+ final CompanionDeviceShellCommand cmd = new CompanionDeviceShellCommand(
+ CompanionDeviceManagerService.this,
+ mAssociationStore,
+ mDevicePresenceMonitor);
+ cmd.exec(this, in, out, err, args, callback, resultReceiver);
}
@Override
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index fd130852a43a..6a19a42723c5 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -21,6 +21,8 @@ import android.os.ShellCommand;
import android.util.Log;
import android.util.Slog;
+import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
+
import java.io.PrintWriter;
import java.util.List;
@@ -29,20 +31,24 @@ class CompanionDeviceShellCommand extends ShellCommand {
private final CompanionDeviceManagerService mService;
private final AssociationStore mAssociationStore;
+ private final CompanionDevicePresenceMonitor mDevicePresenceMonitor;
CompanionDeviceShellCommand(CompanionDeviceManagerService service,
- AssociationStore associationStore) {
+ AssociationStore associationStore,
+ CompanionDevicePresenceMonitor devicePresenceMonitor) {
mService = service;
mAssociationStore = associationStore;
+ mDevicePresenceMonitor = devicePresenceMonitor;
}
@Override
public int onCommand(String cmd) {
final PrintWriter out = getOutPrintWriter();
+ final int associationId;
try {
switch (cmd) {
case "list": {
- final int userId = getNextArgInt();
+ final int userId = getNextIntArgRequired();
final List<AssociationInfo> associationsForUser =
mAssociationStore.getAssociationsForUser(userId);
for (AssociationInfo association : associationsForUser) {
@@ -55,7 +61,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
break;
case "associate": {
- int userId = getNextArgInt();
+ int userId = getNextIntArgRequired();
String packageName = getNextArgRequired();
String address = getNextArgRequired();
mService.legacyCreateAssociation(userId, address, packageName, null);
@@ -63,7 +69,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
break;
case "disassociate": {
- final int userId = getNextArgInt();
+ final int userId = getNextIntArgRequired();
final String packageName = getNextArgRequired();
final String address = getNextArgRequired();
final AssociationInfo association =
@@ -80,6 +86,16 @@ class CompanionDeviceShellCommand extends ShellCommand {
}
break;
+ case "simulate-device-appeared":
+ associationId = getNextIntArgRequired();
+ mDevicePresenceMonitor.simulateDeviceAppeared(associationId);
+ break;
+
+ case "simulate-device-disappeared":
+ associationId = getNextIntArgRequired();
+ mDevicePresenceMonitor.simulateDeviceDisappeared(associationId);
+ break;
+
default:
return handleDefaultCommands(cmd);
}
@@ -91,10 +107,6 @@ class CompanionDeviceShellCommand extends ShellCommand {
}
}
- private int getNextArgInt() {
- return Integer.parseInt(getNextArgRequired());
- }
-
@Override
public void onHelp() {
PrintWriter pw = getOutPrintWriter();
@@ -108,7 +120,31 @@ class CompanionDeviceShellCommand extends ShellCommand {
pw.println(" disassociate USER_ID PACKAGE MAC_ADDRESS");
pw.println(" Remove an existing Association.");
pw.println(" clear-association-memory-cache");
- pw.println(" Clear the in-memory association cache and reload all association "
- + "information from persistent storage. USE FOR DEBUGGING PURPOSES ONLY.");
+ pw.println(" Clear the in-memory association cache and reload all association ");
+ pw.println(" information from persistent storage. USE FOR DEBUGGING PURPOSES ONLY.");
+ pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
+
+ pw.println(" simulate-device-appeared ASSOCIATION_ID");
+ pw.println(" Make CDM act as if the given companion device has appeared.");
+ pw.println(" I.e. bind the associated companion application's");
+ pw.println(" CompanionDeviceService(s) and trigger onDeviceAppeared() callback.");
+ pw.println(" The CDM will consider the devices as present for 60 seconds and then");
+ pw.println(" will act as if device disappeared, unless 'simulate-device-disappeared'");
+ pw.println(" or 'simulate-device-appeared' is called again before 60 seconds run out"
+ + ".");
+ pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
+
+ pw.println(" simulate-device-disappeared ASSOCIATION_ID");
+ pw.println(" Make CDM act as if the given companion device has disappeared.");
+ pw.println(" I.e. unbind the associated companion application's");
+ pw.println(" CompanionDeviceService(s) and trigger onDeviceDisappeared() callback.");
+ pw.println(" NOTE: This will only have effect if 'simulate-device-appeared' was");
+ pw.println(" invoked for the same device (same ASSOCIATION_ID) no longer than");
+ pw.println(" 60 seconds ago.");
+ pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
+ }
+
+ private int getNextIntArgRequired() {
+ return Integer.parseInt(getNextArgRequired());
}
}
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
index 6371b25e6347..37e83697c787 100644
--- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
+++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
@@ -16,11 +16,19 @@
package com.android.server.companion.presence;
+import static android.os.Process.ROOT_UID;
+import static android.os.Process.SHELL_UID;
+
import android.annotation.NonNull;
import android.annotation.SuppressLint;
+import android.annotation.TestApi;
import android.bluetooth.BluetoothAdapter;
import android.companion.AssociationInfo;
import android.content.Context;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
import android.util.Log;
import com.android.server.companion.AssociationStore;
@@ -72,6 +80,11 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange
private final @NonNull Set<Integer> mNearbyBleDevices = new HashSet<>();
private final @NonNull Set<Integer> mReportedSelfManagedDevices = new HashSet<>();
+ // Tracking "simulated" presence. Used for debugging and testing only.
+ private final @NonNull Set<Integer> mSimulated = new HashSet<>();
+ private final SimulatedDevicePresenceSchedulerHelper mSchedulerHelper =
+ new SimulatedDevicePresenceSchedulerHelper();
+
public CompanionDevicePresenceMonitor(@NonNull AssociationStore associationStore,
@NonNull Callback callback) {
mAssociationStore = associationStore;
@@ -106,7 +119,8 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange
public boolean isDevicePresent(int associationId) {
return mReportedSelfManagedDevices.contains(associationId)
|| mConnectedBtDevices.contains(associationId)
- || mNearbyBleDevices.contains(associationId);
+ || mNearbyBleDevices.contains(associationId)
+ || mSimulated.contains(associationId);
}
/**
@@ -155,6 +169,45 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange
onDeviceGone(mNearbyBleDevices, associationId, /* sourceLoggingTag */ "ble");
}
+ /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */
+ @TestApi
+ public void simulateDeviceAppeared(int associationId) {
+ // IMPORTANT: this API should only be invoked via the
+ // 'companiondevice simulate-device-appeared' Shell command, so the only uid-s allowed to
+ // make this call are SHELL and ROOT.
+ // No other caller (including SYSTEM!) should be allowed.
+ enforceCallerShellOrRoot();
+ // Make sure the association exists.
+ enforceAssociationExists(associationId);
+
+ onDevicePresent(mSimulated, associationId, /* sourceLoggingTag */ "simulated");
+
+ mSchedulerHelper.scheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId);
+ }
+
+ /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */
+ @TestApi
+ public void simulateDeviceDisappeared(int associationId) {
+ // IMPORTANT: this API should only be invoked via the
+ // 'companiondevice simulate-device-appeared' Shell command, so the only uid-s allowed to
+ // make this call are SHELL and ROOT.
+ // No other caller (including SYSTEM!) should be allowed.
+ enforceCallerShellOrRoot();
+ // Make sure the association exists.
+ enforceAssociationExists(associationId);
+
+ mSchedulerHelper.unscheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId);
+
+ onDeviceGone(mSimulated, associationId, /* sourceLoggingTag */ "simulated");
+ }
+
+ private void enforceAssociationExists(int associationId) {
+ if (mAssociationStore.getAssociationById(associationId) == null) {
+ throw new IllegalArgumentException(
+ "Association with id " + associationId + " does not exist.");
+ }
+ }
+
private void onDevicePresent(@NonNull Set<Integer> presentDevicesForSource,
int newDeviceAssociationId, @NonNull String sourceLoggingTag) {
if (DEBUG) {
@@ -206,6 +259,16 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange
}
/**
+ * Remove the current connected devices by associationId.
+ */
+ public void removeDeviceFromMonitoring(int associationId) {
+ mConnectedBtDevices.remove(associationId);
+ mNearbyBleDevices.remove(associationId);
+ mReportedSelfManagedDevices.remove(associationId);
+ mSimulated.remove(associationId);
+ }
+
+ /**
* Implements
* {@link AssociationStore.OnChangeListener#onAssociationRemoved(AssociationInfo)}
*/
@@ -217,12 +280,42 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange
Log.d(TAG, " > association=" + association);
}
- mConnectedBtDevices.remove(id);
- mNearbyBleDevices.remove(id);
- mReportedSelfManagedDevices.remove(id);
+ removeDeviceFromMonitoring(id);
// Do NOT call mCallback.onDeviceDisappeared()!
// CompanionDeviceManagerService will know that the association is removed, and will do
// what's needed.
}
+
+ private static void enforceCallerShellOrRoot() {
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid == SHELL_UID || callingUid == ROOT_UID) return;
+
+ throw new SecurityException("Caller is neither Shell nor Root");
+ }
+
+ private class SimulatedDevicePresenceSchedulerHelper extends Handler {
+ SimulatedDevicePresenceSchedulerHelper() {
+ super(Looper.getMainLooper());
+ }
+
+ void scheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId) {
+ // First, unschedule if it was scheduled previously.
+ if (hasMessages(/* what */ associationId)) {
+ removeMessages(/* what */ associationId);
+ }
+
+ sendEmptyMessageDelayed(/* what */ associationId, 60 * 1000 /* 60 seconds */);
+ }
+
+ void unscheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId) {
+ removeMessages(/* what */ associationId);
+ }
+
+ @Override
+ public void handleMessage(@NonNull Message msg) {
+ final int associationId = msg.what;
+ onDeviceGone(mSimulated, associationId, /* sourceLoggingTag */ "simulated");
+ }
+ }
}
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 9d4b50be41fb..80182d26003d 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -22,6 +22,7 @@ import android.annotation.StringDef;
import android.graphics.Point;
import android.graphics.PointF;
import android.hardware.display.DisplayManagerInternal;
+import android.hardware.input.InputDeviceIdentifier;
import android.hardware.input.InputManager;
import android.hardware.input.InputManagerInternal;
import android.hardware.input.VirtualKeyEvent;
@@ -29,11 +30,13 @@ import android.hardware.input.VirtualMouseButtonEvent;
import android.hardware.input.VirtualMouseRelativeEvent;
import android.hardware.input.VirtualMouseScrollEvent;
import android.hardware.input.VirtualTouchEvent;
+import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Slog;
import android.view.Display;
+import android.view.InputDevice;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -44,7 +47,11 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Iterator;
import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Supplier;
/** Controls virtual input devices, including device lifecycle and event dispatch. */
class InputController {
@@ -72,20 +79,27 @@ class InputController {
@GuardedBy("mLock")
final Map<IBinder, InputDeviceDescriptor> mInputDeviceDescriptors = new ArrayMap<>();
+ private final Handler mHandler;
private final NativeWrapper mNativeWrapper;
private final DisplayManagerInternal mDisplayManagerInternal;
private final InputManagerInternal mInputManagerInternal;
+ private final DeviceCreationThreadVerifier mThreadVerifier;
- InputController(@NonNull Object lock) {
- this(lock, new NativeWrapper());
+ InputController(@NonNull Object lock, @NonNull Handler handler) {
+ this(lock, new NativeWrapper(), handler,
+ // Verify that virtual devices are not created on the handler thread.
+ () -> !handler.getLooper().isCurrentThread());
}
@VisibleForTesting
- InputController(@NonNull Object lock, @NonNull NativeWrapper nativeWrapper) {
+ InputController(@NonNull Object lock, @NonNull NativeWrapper nativeWrapper,
+ @NonNull Handler handler, @NonNull DeviceCreationThreadVerifier threadVerifier) {
mLock = lock;
+ mHandler = handler;
mNativeWrapper = nativeWrapper;
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
+ mThreadVerifier = threadVerifier;
}
void close() {
@@ -108,23 +122,13 @@ class InputController {
@NonNull IBinder deviceToken,
int displayId) {
final String phys = createPhys(PHYS_TYPE_KEYBOARD);
- setUniqueIdAssociation(displayId, phys);
- final int fd = mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId, phys);
- if (fd < 0) {
- throw new RuntimeException(
- "A native error occurred when creating keyboard: " + -fd);
- }
- final BinderDeathRecipient binderDeathRecipient = new BinderDeathRecipient(deviceToken);
- synchronized (mLock) {
- mInputDeviceDescriptors.put(deviceToken,
- new InputDeviceDescriptor(fd, binderDeathRecipient,
- InputDeviceDescriptor.TYPE_KEYBOARD, displayId, phys));
- }
try {
- deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
- } catch (RemoteException e) {
- // TODO(b/215608394): remove and close InputDeviceDescriptor
- throw new RuntimeException("Could not create virtual keyboard", e);
+ createDeviceInternal(InputDeviceDescriptor.TYPE_KEYBOARD, deviceName, vendorId,
+ productId, deviceToken, displayId, phys,
+ () -> mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId, phys));
+ } catch (DeviceCreationException e) {
+ throw new RuntimeException(
+ "Failed to create virtual keyboard device '" + deviceName + "'.", e);
}
}
@@ -134,25 +138,15 @@ class InputController {
@NonNull IBinder deviceToken,
int displayId) {
final String phys = createPhys(PHYS_TYPE_MOUSE);
- setUniqueIdAssociation(displayId, phys);
- final int fd = mNativeWrapper.openUinputMouse(deviceName, vendorId, productId, phys);
- if (fd < 0) {
- throw new RuntimeException(
- "A native error occurred when creating mouse: " + -fd);
- }
- final BinderDeathRecipient binderDeathRecipient = new BinderDeathRecipient(deviceToken);
- synchronized (mLock) {
- mInputDeviceDescriptors.put(deviceToken,
- new InputDeviceDescriptor(fd, binderDeathRecipient,
- InputDeviceDescriptor.TYPE_MOUSE, displayId, phys));
- mInputManagerInternal.setVirtualMousePointerDisplayId(displayId);
- }
try {
- deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
- } catch (RemoteException e) {
- // TODO(b/215608394): remove and close InputDeviceDescriptor
- throw new RuntimeException("Could not create virtual mouse", e);
+ createDeviceInternal(InputDeviceDescriptor.TYPE_MOUSE, deviceName, vendorId, productId,
+ deviceToken, displayId, phys,
+ () -> mNativeWrapper.openUinputMouse(deviceName, vendorId, productId, phys));
+ } catch (DeviceCreationException e) {
+ throw new RuntimeException(
+ "Failed to create virtual mouse device: '" + deviceName + "'.", e);
}
+ mInputManagerInternal.setVirtualMousePointerDisplayId(displayId);
}
void createTouchscreen(@NonNull String deviceName,
@@ -162,24 +156,14 @@ class InputController {
int displayId,
@NonNull Point screenSize) {
final String phys = createPhys(PHYS_TYPE_TOUCHSCREEN);
- setUniqueIdAssociation(displayId, phys);
- final int fd = mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId, phys,
- screenSize.y, screenSize.x);
- if (fd < 0) {
- throw new RuntimeException(
- "A native error occurred when creating touchscreen: " + -fd);
- }
- final BinderDeathRecipient binderDeathRecipient = new BinderDeathRecipient(deviceToken);
- synchronized (mLock) {
- mInputDeviceDescriptors.put(deviceToken,
- new InputDeviceDescriptor(fd, binderDeathRecipient,
- InputDeviceDescriptor.TYPE_TOUCHSCREEN, displayId, phys));
- }
try {
- deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
- } catch (RemoteException e) {
- // TODO(b/215608394): remove and close InputDeviceDescriptor
- throw new RuntimeException("Could not create virtual touchscreen", e);
+ createDeviceInternal(InputDeviceDescriptor.TYPE_TOUCHSCREEN, deviceName, vendorId,
+ productId, deviceToken, displayId, phys,
+ () -> mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId,
+ phys, screenSize.y, screenSize.x));
+ } catch (DeviceCreationException e) {
+ throw new RuntimeException(
+ "Failed to create virtual touchscreen device '" + deviceName + "'.", e);
}
}
@@ -510,4 +494,133 @@ class InputController {
unregisterInputDevice(mDeviceToken);
}
}
+
+ /** A helper class used to wait for an input device to be registered. */
+ private class WaitForDevice implements AutoCloseable {
+ private final CountDownLatch mDeviceAddedLatch = new CountDownLatch(1);
+ private final InputManager.InputDeviceListener mListener;
+
+ WaitForDevice(String deviceName, int vendorId, int productId) {
+ mListener = new InputManager.InputDeviceListener() {
+ @Override
+ public void onInputDeviceAdded(int deviceId) {
+ final InputDevice device = InputManager.getInstance().getInputDevice(
+ deviceId);
+ Objects.requireNonNull(device, "Newly added input device was null.");
+ if (!device.getName().equals(deviceName)) {
+ return;
+ }
+ final InputDeviceIdentifier id = device.getIdentifier();
+ if (id.getVendorId() != vendorId || id.getProductId() != productId) {
+ return;
+ }
+ mDeviceAddedLatch.countDown();
+ }
+
+ @Override
+ public void onInputDeviceRemoved(int deviceId) {
+
+ }
+
+ @Override
+ public void onInputDeviceChanged(int deviceId) {
+
+ }
+ };
+ InputManager.getInstance().registerInputDeviceListener(mListener, mHandler);
+ }
+
+ /** Note: This must not be called from {@link #mHandler}'s thread. */
+ void waitForDeviceCreation() throws DeviceCreationException {
+ try {
+ if (!mDeviceAddedLatch.await(1, TimeUnit.MINUTES)) {
+ throw new DeviceCreationException(
+ "Timed out waiting for virtual device to be created.");
+ }
+ } catch (InterruptedException e) {
+ throw new DeviceCreationException(
+ "Interrupted while waiting for virtual device to be created.", e);
+ }
+ }
+
+ @Override
+ public void close() {
+ InputManager.getInstance().unregisterInputDeviceListener(mListener);
+ }
+ }
+
+ /** An internal exception that is thrown to indicate an error when opening a virtual device. */
+ private static class DeviceCreationException extends Exception {
+ DeviceCreationException(String message) {
+ super(message);
+ }
+ DeviceCreationException(String message, Exception cause) {
+ super(message, cause);
+ }
+ }
+
+ /**
+ * Creates a virtual input device synchronously, and waits for the notification that the device
+ * was added.
+ *
+ * Note: Input device creation is expected to happen on a binder thread, and the calling thread
+ * will be blocked until the input device creation is successful. This should not be called on
+ * the handler's thread.
+ *
+ * @throws DeviceCreationException Throws this exception if anything unexpected happens in the
+ * process of creating the device. This method will take care
+ * to restore the state of the system in the event of any
+ * unexpected behavior.
+ */
+ private void createDeviceInternal(@InputDeviceDescriptor.Type int type, String deviceName,
+ int vendorId, int productId, IBinder deviceToken, int displayId, String phys,
+ Supplier<Integer> deviceOpener)
+ throws DeviceCreationException {
+ if (!mThreadVerifier.isValidThread()) {
+ throw new IllegalStateException(
+ "Virtual device creation should happen on an auxiliary thread (e.g. binder "
+ + "thread) and not from the handler's thread.");
+ }
+
+ final int fd;
+ final BinderDeathRecipient binderDeathRecipient;
+
+ setUniqueIdAssociation(displayId, phys);
+ try (WaitForDevice waiter = new WaitForDevice(deviceName, vendorId, productId)) {
+ fd = deviceOpener.get();
+ if (fd < 0) {
+ throw new DeviceCreationException(
+ "A native error occurred when creating touchscreen: " + -fd);
+ }
+ // The fd is valid from here, so ensure that all failures close the fd after this point.
+ try {
+ waiter.waitForDeviceCreation();
+
+ binderDeathRecipient = new BinderDeathRecipient(deviceToken);
+ try {
+ deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
+ } catch (RemoteException e) {
+ throw new DeviceCreationException(
+ "Client died before virtual device could be created.", e);
+ }
+ } catch (DeviceCreationException e) {
+ mNativeWrapper.closeUinput(fd);
+ throw e;
+ }
+ } catch (DeviceCreationException e) {
+ InputManager.getInstance().removeUniqueIdAssociation(phys);
+ throw e;
+ }
+
+ synchronized (mLock) {
+ mInputDeviceDescriptors.put(deviceToken,
+ new InputDeviceDescriptor(fd, binderDeathRecipient, type, displayId, phys));
+ }
+ }
+
+ @VisibleForTesting
+ interface DeviceCreationThreadVerifier {
+ /** Returns true if the calling thread is a valid thread for device creation. */
+ boolean isValidThread();
+ }
}
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 de14ef61a075..9802b9783da2 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -166,7 +166,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
mAppToken = token;
mParams = params;
if (inputController == null) {
- mInputController = new InputController(mVirtualDeviceLock);
+ mInputController = new InputController(
+ mVirtualDeviceLock, context.getMainThreadHandler());
} else {
mInputController = inputController;
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 8f37823e4f9a..bc40170d39b7 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -3055,19 +3055,7 @@ class StorageManagerService extends IStorageManager.Stub
return true;
}
- if (packageName == null) {
- return false;
- }
-
- final int packageUid = mPmInternal.getPackageUid(packageName,
- PackageManager.MATCH_DEBUG_TRIAGED_MISSING, UserHandle.getUserId(callerUid));
-
- if (DEBUG_OBB) {
- Slog.d(TAG, "packageName = " + packageName + ", packageUid = " +
- packageUid + ", callerUid = " + callerUid);
- }
-
- return callerUid == packageUid;
+ return mPmInternal.isSameApp(packageName, callerUid, UserHandle.getUserId(callerUid));
}
@Override
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2ceb00d1ac02..dea8cc015272 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -89,7 +89,6 @@ import static android.os.Process.killProcessQuiet;
import static android.os.Process.myPid;
import static android.os.Process.myUid;
import static android.os.Process.readProcFile;
-import static android.os.Process.removeAllProcessGroups;
import static android.os.Process.sendSignal;
import static android.os.Process.setThreadPriority;
import static android.os.Process.setThreadScheduler;
@@ -2440,8 +2439,6 @@ public class ActivityManagerService extends IActivityManager.Stub
}
private void start() {
- removeAllProcessGroups();
-
mBatteryStatsService.publish();
mAppOpsService.publish();
mProcessStats.publish();
diff --git a/services/core/java/com/android/server/am/AppBatteryTracker.java b/services/core/java/com/android/server/am/AppBatteryTracker.java
index 6e28d8fbad59..5a234f5010dd 100644
--- a/services/core/java/com/android/server/am/AppBatteryTracker.java
+++ b/services/core/java/com/android/server/am/AppBatteryTracker.java
@@ -56,7 +56,6 @@ import android.os.BatteryConsumer.Dimensions;
import android.os.BatteryStatsInternal;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
-import android.os.Build;
import android.os.PowerExemptionManager;
import android.os.PowerExemptionManager.ReasonCode;
import android.os.SystemClock;
@@ -97,7 +96,7 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy>
static final boolean DEBUG_BACKGROUND_BATTERY_TRACKER = false;
static final boolean DEBUG_BACKGROUND_BATTERY_TRACKER_VERBOSE =
- DEBUG_BACKGROUND_BATTERY_TRACKER | Build.IS_DEBUGGABLE;
+ DEBUG_BACKGROUND_BATTERY_TRACKER | false;
// As we don't support realtime per-UID battery usage stats yet, we're polling the stats
// in a regular time basis.
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index f7abb117e3de..6f5d87bab15f 100644
--- a/services/core/java/com/android/server/am/AppRestrictionController.java
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -265,6 +265,20 @@ public final class AppRestrictionController {
*/
private int[] mDeviceIdleExceptIdleAllowlist = new int[0]; // No lock is needed.
+ /**
+ * The pre-configured system app-ids in the power-save allow list.
+ *
+ * @see #mDeviceIdleAllowlist.
+ */
+ private final ArraySet<Integer> mSystemDeviceIdleAllowlist = new ArraySet<>();
+
+ /**
+ * The pre-configured system app-ids in the power-save allow list, except-idle.
+ *
+ * @see #mDeviceIdleExceptIdleAllowlist.
+ */
+ private final ArraySet<Integer> mSystemDeviceIdleExceptIdleAllowlist = new ArraySet<>();
+
private final Object mLock = new Object();
private final Object mSettingsLock = new Object();
private final Injector mInjector;
@@ -1511,14 +1525,33 @@ public final class AppRestrictionController {
}
private void initBgRestrictionExemptioFromSysConfig() {
- mBgRestrictionExemptioFromSysConfig =
- SystemConfig.getInstance().getBgRestrictionExemption();
+ final SystemConfig sysConfig = SystemConfig.getInstance();
+ mBgRestrictionExemptioFromSysConfig = sysConfig.getBgRestrictionExemption();
if (DEBUG_BG_RESTRICTION_CONTROLLER) {
final ArraySet<String> exemptedPkgs = mBgRestrictionExemptioFromSysConfig;
for (int i = exemptedPkgs.size() - 1; i >= 0; i--) {
Slog.i(TAG, "bg-restriction-exemption: " + exemptedPkgs.valueAt(i));
}
}
+ loadAppIdsFromPackageList(sysConfig.getAllowInPowerSaveExceptIdle(),
+ mSystemDeviceIdleExceptIdleAllowlist);
+ loadAppIdsFromPackageList(sysConfig.getAllowInPowerSave(), mSystemDeviceIdleAllowlist);
+ }
+
+ private void loadAppIdsFromPackageList(ArraySet<String> packages, ArraySet<Integer> apps) {
+ final PackageManager pm = mInjector.getPackageManager();
+ for (int i = packages.size() - 1; i >= 0; i--) {
+ final String pkg = packages.valueAt(i);
+ try {
+ final ApplicationInfo ai = pm.getApplicationInfo(pkg,
+ PackageManager.MATCH_SYSTEM_ONLY);
+ if (ai == null) {
+ continue;
+ }
+ apps.add(UserHandle.getAppId(ai.uid));
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+ }
}
private boolean isExemptedFromSysConfig(String packageName) {
@@ -2685,6 +2718,13 @@ public final class AppRestrictionController {
|| Arrays.binarySearch(mDeviceIdleExceptIdleAllowlist, appId) >= 0;
}
+ boolean isOnSystemDeviceIdleAllowlist(int uid) {
+ final int appId = UserHandle.getAppId(uid);
+
+ return mSystemDeviceIdleAllowlist.contains(appId)
+ || mSystemDeviceIdleExceptIdleAllowlist.contains(appId);
+ }
+
void setDeviceIdleAllowlist(int[] allAppids, int[] exceptIdleAppids) {
mDeviceIdleAllowlist = allAppids;
mDeviceIdleExceptIdleAllowlist = exceptIdleAppids;
@@ -2703,6 +2743,9 @@ public final class AppRestrictionController {
if (UserHandle.isCore(uid)) {
return REASON_SYSTEM_UID;
}
+ if (isOnSystemDeviceIdleAllowlist(uid)) {
+ return REASON_SYSTEM_ALLOW_LISTED;
+ }
if (isOnDeviceIdleAllowlist(uid)) {
return REASON_ALLOWLISTED_PACKAGE;
}
@@ -2748,7 +2791,7 @@ public final class AppRestrictionController {
} else if (isExemptedFromSysConfig(pkg)) {
return REASON_SYSTEM_ALLOW_LISTED;
} else if (mConstantsObserver.mBgRestrictionExemptedPackages.contains(pkg)) {
- return REASON_ALLOWLISTED_PACKAGE;
+ return REASON_SYSTEM_ALLOW_LISTED;
} else if (pm.isPackageStateProtected(pkg, userId)) {
return REASON_DPO_PROTECTED_APP;
}
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index a172018ab291..e49497e8688c 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -479,19 +479,33 @@ public final class CachedAppOptimizer {
@GuardedBy("mProcLock")
void compactAppSome(ProcessRecord app, boolean force) {
app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_SOME);
- if (DEBUG_COMPACTION) {
- Slog.d(TAG_AM, " compactAppSome requested for " + app.processName + " force: " + force);
+ compactApp(app, force, "some");
+ }
+
+ // This method returns true only if requirements are met. Note, that requirements are different
+ // from throttles applied at the time a compaction is trying to be executed in the sense that
+ // these are not subject to change dependent on time or memory as throttles usually do.
+ @GuardedBy("mProcLock")
+ boolean meetsCompactionRequirements(ProcessRecord proc) {
+ if (mAm.mInternal.isPendingTopUid(proc.uid)) {
+ // In case the OOM Adjust has not yet been propagated we see if this is
+ // pending on becoming top app in which case we should not compact.
+ if (DEBUG_COMPACTION) {
+ Slog.d(TAG_AM, "Skip compaction since UID is active for " + proc.processName);
+ }
+ return false;
}
- if (force || !app.mOptRecord.hasPendingCompact()) {
- Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_COMPACTION_TRACK,
- "compactAppSome " + app.processName != null ? app.processName : "");
- app.mOptRecord.setHasPendingCompact(true);
- app.mOptRecord.setForceCompact(force);
- mPendingCompactionProcesses.add(app);
- mCompactionHandler.sendMessage(
- mCompactionHandler.obtainMessage(
- COMPACT_PROCESS_MSG, app.mState.getSetAdj(), app.mState.getSetProcState()));
+
+ if (proc.mState.hasForegroundActivities()) {
+ if (DEBUG_COMPACTION) {
+ Slog.e(TAG_AM,
+ "Skip compaction as process " + proc.processName
+ + " has foreground activities");
+ }
+ return false;
}
+
+ return true;
}
@GuardedBy("mProcLock")
@@ -508,19 +522,7 @@ public final class CachedAppOptimizer {
// Apply OOM adj score throttle for Full App Compaction.
if (force || oomAdjEnteredCached) {
app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_FULL);
- if (!app.mOptRecord.hasPendingCompact()) {
- Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_COMPACTION_TRACK,
- "compactAppFull " + app.processName != null ? app.processName : "");
- app.mOptRecord.setHasPendingCompact(true);
- app.mOptRecord.setForceCompact(force);
- mPendingCompactionProcesses.add(app);
- mCompactionHandler.sendMessage(mCompactionHandler.obtainMessage(
- COMPACT_PROCESS_MSG, app.mState.getSetAdj(), app.mState.getSetProcState()));
- } else if (DEBUG_COMPACTION) {
- Slog.d(TAG_AM,
- " compactAppFull Skipped for " + app.processName
- + " since it has a pending compact");
- }
+ compactApp(app, force, "Full");
} else {
if (DEBUG_COMPACTION) {
Slog.d(TAG_AM, "Skipping full compaction for " + app.processName
@@ -533,15 +535,34 @@ public final class CachedAppOptimizer {
@GuardedBy("mProcLock")
void compactAppPersistent(ProcessRecord app) {
app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_PERSISTENT);
- if (!app.mOptRecord.hasPendingCompact()) {
+ compactApp(app, false, "Persistent");
+ }
+
+ @GuardedBy("mProcLock")
+ boolean compactApp(ProcessRecord app, boolean force, String compactRequestType) {
+ if (!app.mOptRecord.hasPendingCompact() && meetsCompactionRequirements(app)) {
+ final String processName = (app.processName != null ? app.processName : "");
+ if (DEBUG_COMPACTION) {
+ Slog.d(TAG_AM, "compactApp " + compactRequestType + " " + processName);
+ }
Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_COMPACTION_TRACK,
- "compactAppPersistent " + app.processName != null ? app.processName : "");
+ "compactApp " + compactRequestType + " " + processName);
app.mOptRecord.setHasPendingCompact(true);
+ app.mOptRecord.setForceCompact(force);
mPendingCompactionProcesses.add(app);
- mCompactionHandler.sendMessage(
- mCompactionHandler.obtainMessage(
+ mCompactionHandler.sendMessage(mCompactionHandler.obtainMessage(
COMPACT_PROCESS_MSG, app.mState.getCurAdj(), app.mState.getSetProcState()));
+ return true;
+ }
+
+ if (DEBUG_COMPACTION) {
+ Slog.d(TAG_AM,
+ " compactApp Skipped for " + app.processName
+ + " pendingCompact= " + app.mOptRecord.hasPendingCompact()
+ + " meetsCompactionRequirements=" + meetsCompactionRequirements(app)
+ + ". Requested compact: " + app.mOptRecord.getReqCompactAction());
}
+ return false;
}
@GuardedBy("mProcLock")
@@ -553,15 +574,7 @@ public final class CachedAppOptimizer {
@GuardedBy("mProcLock")
void compactAppBfgs(ProcessRecord app) {
app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_BFGS);
- if (!app.mOptRecord.hasPendingCompact()) {
- Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_COMPACTION_TRACK,
- "compactAppBfgs " + app.processName != null ? app.processName : "");
- app.mOptRecord.setHasPendingCompact(true);
- mPendingCompactionProcesses.add(app);
- mCompactionHandler.sendMessage(
- mCompactionHandler.obtainMessage(
- COMPACT_PROCESS_MSG, app.mState.getCurAdj(), app.mState.getSetProcState()));
- }
+ compactApp(app, false, " Bfgs");
}
@GuardedBy("mProcLock")
@@ -572,6 +585,9 @@ public final class CachedAppOptimizer {
void compactAllSystem() {
if (useCompaction()) {
+ if (DEBUG_COMPACTION) {
+ Slog.d(TAG_AM, "compactAllSystem");
+ }
Trace.instantForTrack(
Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_COMPACTION_TRACK, "compactAllSystem");
mCompactionHandler.sendMessage(mCompactionHandler.obtainMessage(
@@ -1175,13 +1191,13 @@ public final class CachedAppOptimizer {
cancelCompaction();
}
- // Perform a minor compaction when a perceptible app becomes the prev/home app
- // Perform a major compaction when any app enters cached
if (oldAdj <= ProcessList.PERCEPTIBLE_APP_ADJ
&& (newAdj == ProcessList.PREVIOUS_APP_ADJ || newAdj == ProcessList.HOME_APP_ADJ)) {
+ // Perform a minor compaction when a perceptible app becomes the prev/home app
compactAppSome(app, false);
} else if (newAdj >= ProcessList.CACHED_APP_MIN_ADJ
&& newAdj <= ProcessList.CACHED_APP_MAX_ADJ) {
+ // Perform a major compaction when any app enters cached
compactAppFull(app, false);
}
}
@@ -1241,12 +1257,6 @@ public final class CachedAppOptimizer {
private boolean shouldOomAdjThrottleCompaction(ProcessRecord proc, int action) {
final String name = proc.processName;
- if (mAm.mInternal.isPendingTopUid(proc.uid)) {
- // In case the OOM Adjust has not yet been propagated we see if this is
- // pending on becoming top app in which case we should not compact.
- Slog.e(TAG_AM, "Skip compaction since UID is active for " + name);
- return true;
- }
// don't compact if the process has returned to perceptible
// and this is only a cached/home/prev compaction
diff --git a/services/core/java/com/android/server/am/ConnectionRecord.java b/services/core/java/com/android/server/am/ConnectionRecord.java
index b6757c8322d4..17fff919ba20 100644
--- a/services/core/java/com/android/server/am/ConnectionRecord.java
+++ b/services/core/java/com/android/server/am/ConnectionRecord.java
@@ -72,7 +72,7 @@ final class ConnectionRecord {
Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
Context.BIND_FOREGROUND_SERVICE,
Context.BIND_TREAT_LIKE_ACTIVITY,
- Context.BIND_VISIBLE,
+ Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE,
Context.BIND_SHOWING_UI,
Context.BIND_NOT_VISIBLE,
Context.BIND_NOT_PERCEPTIBLE,
@@ -225,8 +225,8 @@ final class ConnectionRecord {
if ((flags & Context.BIND_SCHEDULE_LIKE_TOP_APP) != 0) {
sb.append("SLTA ");
}
- if ((flags&Context.BIND_VISIBLE) != 0) {
- sb.append("VIS ");
+ if ((flags & Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE) != 0) {
+ sb.append("VFGS ");
}
if ((flags&Context.BIND_SHOWING_UI) != 0) {
sb.append("UI ");
diff --git a/services/core/java/com/android/server/am/DropboxRateLimiter.java b/services/core/java/com/android/server/am/DropboxRateLimiter.java
index 18fb6a480f29..08a6719a016a 100644
--- a/services/core/java/com/android/server/am/DropboxRateLimiter.java
+++ b/services/core/java/com/android/server/am/DropboxRateLimiter.java
@@ -24,9 +24,14 @@ import com.android.internal.annotations.GuardedBy;
/** Rate limiter for adding errors into dropbox. */
public class DropboxRateLimiter {
- private static final long RATE_LIMIT_BUFFER_EXPIRY = 15 * DateUtils.SECOND_IN_MILLIS;
- private static final long RATE_LIMIT_BUFFER_DURATION = 10 * DateUtils.SECOND_IN_MILLIS;
- private static final int RATE_LIMIT_ALLOWED_ENTRIES = 5;
+ // After RATE_LIMIT_ALLOWED_ENTRIES have been collected (for a single breakdown of
+ // process/eventType) further entries will be rejected until RATE_LIMIT_BUFFER_DURATION has
+ // elapsed, after which the current count for this breakdown will be reset.
+ private static final long RATE_LIMIT_BUFFER_DURATION = 10 * DateUtils.MINUTE_IN_MILLIS;
+ // The time duration after which the rate limit buffer will be cleared.
+ private static final long RATE_LIMIT_BUFFER_EXPIRY = 3 * RATE_LIMIT_BUFFER_DURATION;
+ // The number of entries to keep per breakdown of process/eventType.
+ private static final int RATE_LIMIT_ALLOWED_ENTRIES = 6;
@GuardedBy("mErrorClusterRecords")
private final ArrayMap<String, ErrorRecord> mErrorClusterRecords = new ArrayMap<>();
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 4d10574149ff..e7fcc5989467 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -39,6 +39,7 @@ import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT_UI;
import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
import static android.app.ActivityManager.PROCESS_STATE_TOP;
import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
+import static android.content.Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE;
@@ -2061,6 +2062,10 @@ public class OomAdjuster {
newAdj = ProcessList.PERCEPTIBLE_APP_ADJ;
} else if (clientAdj >= ProcessList.PERCEPTIBLE_APP_ADJ) {
newAdj = clientAdj;
+ } else if (cr.hasFlag(BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE)
+ && clientAdj <= ProcessList.VISIBLE_APP_ADJ
+ && adj > ProcessList.VISIBLE_APP_ADJ) {
+ newAdj = ProcessList.VISIBLE_APP_ADJ;
} else {
if (adj > ProcessList.VISIBLE_APP_ADJ) {
// TODO: Is this too limiting for apps bound from TOP?
@@ -2097,7 +2102,9 @@ public class OomAdjuster {
// processes). These should not bring the current process
// into the top state, since they are not on top. Instead
// give them the best bound state after that.
- if (cr.hasFlag(Context.BIND_FOREGROUND_SERVICE)) {
+ if (cr.hasFlag(BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE)) {
+ clientProcState = PROCESS_STATE_FOREGROUND_SERVICE;
+ } else if (cr.hasFlag(Context.BIND_FOREGROUND_SERVICE)) {
clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
} else if (mService.mWakefulness.get()
== PowerManagerInternal.WAKEFULNESS_AWAKE
@@ -2556,20 +2563,21 @@ public class OomAdjuster {
// reminder: here, setAdj is previous state, curAdj is upcoming state
if (state.getCurAdj() != state.getSetAdj()) {
mCachedAppOptimizer.onOomAdjustChanged(state.getSetAdj(), state.getCurAdj(), app);
- } else if (mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE
- && state.getSetAdj() < ProcessList.FOREGROUND_APP_ADJ
- && !state.isRunningRemoteAnimation()
- // Because these can fire independent of oom_adj/procstate changes, we need
- // to throttle the actual dispatch of these requests in addition to the
- // processing of the requests. As a result, there is throttling both here
- // and in CachedAppOptimizer.
- && mCachedAppOptimizer.shouldCompactPersistent(app, now)) {
- mCachedAppOptimizer.compactAppPersistent(app);
- } else if (mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE
- && state.getCurProcState()
- == ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
- && mCachedAppOptimizer.shouldCompactBFGS(app, now)) {
- mCachedAppOptimizer.compactAppBfgs(app);
+ } else if (mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE) {
+ // See if we can compact persistent and bfgs services now that screen is off
+ if (state.getSetAdj() < ProcessList.FOREGROUND_APP_ADJ
+ && !state.isRunningRemoteAnimation()
+ // Because these can fire independent of oom_adj/procstate changes, we need
+ // to throttle the actual dispatch of these requests in addition to the
+ // processing of the requests. As a result, there is throttling both here
+ // and in CachedAppOptimizer.
+ && mCachedAppOptimizer.shouldCompactPersistent(app, now)) {
+ mCachedAppOptimizer.compactAppPersistent(app);
+ } else if (state.getCurProcState()
+ == ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
+ && mCachedAppOptimizer.shouldCompactBFGS(app, now)) {
+ mCachedAppOptimizer.compactAppBfgs(app);
+ }
}
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index fc73a5955001..cceacd8c6fa3 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2792,6 +2792,15 @@ public final class ProcessList {
}
int N = procs.size();
+ for (int i = 0; i < N; ++i) {
+ final ProcessRecord proc = procs.get(i).first;
+ try {
+ Process.setProcessFrozen(proc.getPid(), proc.uid, true);
+ } catch (Exception e) {
+ Slog.w(TAG, "Unable to freeze " + proc.getPid() + " " + proc.processName);
+ }
+ }
+
for (int i=0; i<N; i++) {
final Pair<ProcessRecord, Boolean> proc = procs.get(i);
removeProcessLocked(proc.first, callerWillRestart, allowRestart || proc.second,
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 3e97b913a158..36afb3677438 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -4545,8 +4545,9 @@ public class AppOpsService extends IAppOpsService.Stub {
* @return The restriction matching the package
*/
private RestrictionBypass getBypassforPackage(@NonNull AndroidPackage pkg) {
- return new RestrictionBypass(pkg.isPrivileged(), mContext.checkPermission(
- android.Manifest.permission.EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS, -1, pkg.getUid())
+ 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);
}
@@ -4853,6 +4854,9 @@ public class AppOpsService extends IAppOpsService.Stub {
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;
}
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 122a950e4fd3..d8aa9aa6591e 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -486,8 +486,7 @@ public class SpatializerHelper {
for (SADeviceState deviceState : mSADevices) {
if (deviceType == deviceState.mDeviceType
- && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
- || !wireless) {
+ && (!wireless || ada.getAddress().equals(deviceState.mDeviceAddress))) {
isInList = true;
if (forceEnable) {
deviceState.mEnabled = true;
@@ -511,8 +510,7 @@ public class SpatializerHelper {
for (SADeviceState deviceState : mSADevices) {
if (deviceType == deviceState.mDeviceType
- && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
- || !wireless) {
+ && (!wireless || ada.getAddress().equals(deviceState.mDeviceAddress))) {
deviceState.mEnabled = false;
break;
}
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index b5c8cd16a355..bc550d311370 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -805,9 +805,10 @@ public class AuthService extends SystemService {
if (isUdfps && udfpsProps.length == 3) {
return new FingerprintSensorPropertiesInternal(sensorId,
Utils.authenticatorStrengthToPropertyStrength(strength), maxEnrollmentsPerUser,
- componentInfo, sensorType, resetLockoutRequiresHardwareAuthToken,
- List.of(new SensorLocationInternal("" /* display */,
- udfpsProps[0], udfpsProps[1], udfpsProps[2])));
+ componentInfo, sensorType, true /* halControlsIllumination */,
+ resetLockoutRequiresHardwareAuthToken,
+ List.of(new SensorLocationInternal("" /* display */, udfpsProps[0],
+ udfpsProps[1], udfpsProps[2])));
} else {
return new FingerprintSensorPropertiesInternal(sensorId,
Utils.authenticatorStrengthToPropertyStrength(strength), maxEnrollmentsPerUser,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 998a8e1e9f90..a600f08efc24 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -176,6 +176,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
prop.commonProps.maxEnrollmentsPerUser,
componentInfo,
prop.sensorType,
+ prop.halControlsIllumination,
true /* resetLockoutRequiresHardwareAuthToken */,
!workaroundLocations.isEmpty() ? workaroundLocations :
Arrays.stream(prop.sensorLocations).map(location ->
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
index 485a674dda92..bea0f4ffd45d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
@@ -400,7 +400,7 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage
.getInteger(R.integer.config_fingerprintMaxTemplatesPerUser);
mSensorProperties = new FingerprintSensorPropertiesInternal(sensorProps.sensorId,
sensorProps.sensorStrength, maxTemplatesAllowed, sensorProps.componentInfo,
- FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL, false /* halControlsIllumination */,
resetLockoutRequiresHardwareAuthToken, sensorProps.getAllLocations());
mMockHalResultController = controller;
mUserHasTrust = new SparseBooleanArray();
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index f3d41c4003c3..2cf11c657aa0 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1440,16 +1440,6 @@ public final class DisplayManagerService extends SystemService {
DisplayDevice device =
mVirtualDisplayAdapter.releaseVirtualDisplayLocked(appToken);
if (device != null) {
- final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device);
- if (display != null) {
- final int displayId = display.getDisplayIdLocked();
- if (mDisplayWindowPolicyControllers.contains(displayId)) {
- Pair<IVirtualDevice, DisplayWindowPolicyController> pair =
- mDisplayWindowPolicyControllers.removeReturnOld(displayId);
- getLocalService(VirtualDeviceManagerInternal.class)
- .onVirtualDisplayRemoved(pair.first, displayId);
- }
- }
// TODO: multi-display - handle virtual displays the same as other display adapters.
mDisplayDeviceRepo.onDisplayDeviceEvent(device,
DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED);
@@ -1609,6 +1599,17 @@ public final class DisplayManagerService extends SystemService {
DisplayManagerGlobal.invalidateLocalDisplayInfoCaches();
sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
scheduleTraversalLocked(false);
+
+ if (mDisplayWindowPolicyControllers.contains(displayId)) {
+ final IVirtualDevice virtualDevice = mDisplayWindowPolicyControllers.removeReturnOld(
+ displayId).first;
+ if (virtualDevice != null) {
+ mHandler.post(() -> {
+ getLocalService(VirtualDeviceManagerInternal.class)
+ .onVirtualDisplayRemoved(virtualDevice, displayId);
+ });
+ }
+ }
}
private void handleLogicalDisplaySwappedLocked(@NonNull LogicalDisplay display) {
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 8ab0b931be11..e4e9d1d49a6e 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -164,6 +164,7 @@ public class InputManagerService extends IInputManager.Stub
private static final int MSG_UPDATE_KEYBOARD_LAYOUTS = 4;
private static final int MSG_RELOAD_DEVICE_ALIASES = 5;
private static final int MSG_DELIVER_TABLET_MODE_CHANGED = 6;
+ private static final int MSG_POINTER_DISPLAY_ID_CHANGED = 7;
private static final int DEFAULT_VIBRATION_MAGNITUDE = 192;
@@ -276,11 +277,24 @@ public class InputManagerService extends IInputManager.Stub
@GuardedBy("mAssociationLock")
private final Map<String, String> mUniqueIdAssociations = 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.
private final Object mAdditionalDisplayInputPropertiesLock = new Object();
- // Forces the MouseCursorController to target a specific display id.
+ // Forces the PointerController to target a specific display id.
@GuardedBy("mAdditionalDisplayInputPropertiesLock")
private int mOverriddenPointerDisplayId = Display.INVALID_DISPLAY;
+
+ // PointerController is the source of truth of the pointer display. This is the value of the
+ // latest pointer display id reported by PointerController.
+ @GuardedBy("mAdditionalDisplayInputPropertiesLock")
+ private int mAcknowledgedPointerDisplayId = Display.INVALID_DISPLAY;
+ // This is the latest display id that IMS has requested PointerController to use. If there are
+ // no devices that can control the pointer, PointerController may end up disregarding this
+ // value.
+ @GuardedBy("mAdditionalDisplayInputPropertiesLock")
+ private int mRequestedPointerDisplayId = Display.INVALID_DISPLAY;
@GuardedBy("mAdditionalDisplayInputPropertiesLock")
private final SparseArray<AdditionalDisplayInputProperties> mAdditionalDisplayInputProperties =
new SparseArray<>();
@@ -289,7 +303,6 @@ public class InputManagerService extends IInputManager.Stub
@GuardedBy("mAdditionalDisplayInputPropertiesLock")
private PointerIcon mIcon;
-
// Holds all the registered gesture monitors that are implemented as spy windows. The spy
// windows are mapped by their InputChannel tokens.
@GuardedBy("mInputMonitors")
@@ -383,6 +396,10 @@ public class InputManagerService extends IInputManager.Stub
NativeInputManagerService getNativeService(InputManagerService service) {
return new NativeInputManagerService.NativeImpl(service, mContext, mLooper.getQueue());
}
+
+ void registerLocalService(InputManagerInternal localService) {
+ LocalServices.addService(InputManagerInternal.class, localService);
+ }
}
public InputManagerService(Context context) {
@@ -391,11 +408,14 @@ public class InputManagerService extends IInputManager.Stub
@VisibleForTesting
InputManagerService(Injector injector) {
+ // The static association map is accessed by both java and native code, so it must be
+ // initialized before initializing the native service.
+ mStaticAssociations = loadStaticInputPortAssociations();
+
mContext = injector.getContext();
mHandler = new InputManagerHandler(injector.getLooper());
mNative = injector.getNativeService(this);
- mStaticAssociations = loadStaticInputPortAssociations();
mUseDevInputEventForAudioJack =
mContext.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack="
@@ -406,7 +426,7 @@ public class InputManagerService extends IInputManager.Stub
mDoubleTouchGestureEnableFile = TextUtils.isEmpty(doubleTouchGestureEnablePath) ? null :
new File(doubleTouchGestureEnablePath);
- LocalServices.addService(InputManagerInternal.class, new LocalService());
+ injector.registerLocalService(new LocalService());
}
public void setWindowManagerCallbacks(WindowManagerCallbacks callbacks) {
@@ -556,6 +576,8 @@ public class InputManagerService extends IInputManager.Stub
vArray[i] = viewports.get(i);
}
mNative.setDisplayViewports(vArray);
+ // Always attempt to update the pointer display when viewports change.
+ updatePointerDisplayId();
if (mOverriddenPointerDisplayId != Display.INVALID_DISPLAY) {
final AdditionalDisplayInputProperties properties =
@@ -1690,6 +1712,13 @@ public class InputManagerService extends IInputManager.Stub
mPointerIconDisplayContext = null;
}
+ synchronized (mAdditionalDisplayInputPropertiesLock) {
+ setPointerIconVisible(AdditionalDisplayInputProperties.DEFAULT_POINTER_ICON_VISIBLE,
+ displayId);
+ setPointerAcceleration(AdditionalDisplayInputProperties.DEFAULT_POINTER_ACCELERATION,
+ displayId);
+ }
+
mNative.displayRemoved(displayId);
}
@@ -1961,10 +1990,43 @@ public class InputManagerService extends IInputManager.Stub
return result;
}
- private void setVirtualMousePointerDisplayId(int displayId) {
+ /**
+ * Update the display on which the mouse pointer is shown.
+ * If there is an overridden display for the mouse pointer, use that. Otherwise, query
+ * WindowManager for the pointer display.
+ *
+ * @return true if the pointer displayId changed, false otherwise.
+ */
+ private boolean updatePointerDisplayId() {
+ synchronized (mAdditionalDisplayInputPropertiesLock) {
+ final int pointerDisplayId = mOverriddenPointerDisplayId != Display.INVALID_DISPLAY
+ ? mOverriddenPointerDisplayId : mWindowManagerCallbacks.getPointerDisplayId();
+ if (mRequestedPointerDisplayId == pointerDisplayId) {
+ return false;
+ }
+ mRequestedPointerDisplayId = pointerDisplayId;
+ mNative.setPointerDisplayId(pointerDisplayId);
+ return true;
+ }
+ }
+
+ private void handlePointerDisplayIdChanged(PointerDisplayIdChangedArgs args) {
+ synchronized (mAdditionalDisplayInputPropertiesLock) {
+ mAcknowledgedPointerDisplayId = args.mPointerDisplayId;
+ // Notify waiting threads that the display of the mouse pointer has changed.
+ mAdditionalDisplayInputPropertiesLock.notifyAll();
+ }
+ mWindowManagerCallbacks.notifyPointerDisplayIdChanged(
+ args.mPointerDisplayId, args.mXPosition, args.mYPosition);
+ }
+
+ private boolean setVirtualMousePointerDisplayIdBlocking(int displayId) {
+ // Indicates whether this request is for removing the override.
+ final boolean removingOverride = displayId == Display.INVALID_DISPLAY;
+
synchronized (mAdditionalDisplayInputPropertiesLock) {
mOverriddenPointerDisplayId = displayId;
- if (displayId != Display.INVALID_DISPLAY) {
+ if (!removingOverride) {
final AdditionalDisplayInputProperties properties =
mAdditionalDisplayInputProperties.get(displayId);
if (properties != null) {
@@ -1972,9 +2034,30 @@ public class InputManagerService extends IInputManager.Stub
updatePointerIconVisibleLocked(properties.pointerIconVisible);
}
}
+ if (!updatePointerDisplayId() && mAcknowledgedPointerDisplayId == displayId) {
+ // The requested pointer display is already set.
+ return true;
+ }
+ if (removingOverride && mAcknowledgedPointerDisplayId == Display.INVALID_DISPLAY) {
+ // The pointer display override is being removed, but the current pointer display
+ // is already invalid. This can happen when the PointerController is destroyed as a
+ // result of the removal of all input devices that can control the pointer.
+ return true;
+ }
+ try {
+ // The pointer display changed, so wait until the change has propagated.
+ mAdditionalDisplayInputPropertiesLock.wait(5_000 /*mills*/);
+ } catch (InterruptedException ignored) {
+ }
+ // This request succeeds in two cases:
+ // - This request was to remove the override, in which case the new pointer display
+ // could be anything that WM has set.
+ // - We are setting a new override, in which case the request only succeeds if the
+ // reported new displayId is the one we requested. This check ensures that if two
+ // competing overrides are requested in succession, the caller can be notified if one
+ // of them fails.
+ return removingOverride || mAcknowledgedPointerDisplayId == displayId;
}
- // TODO(b/215597605): trigger MousePositionTracker update
- mNative.notifyPointerDisplayIdChanged();
}
private int getVirtualMousePointerDisplayId() {
@@ -3156,18 +3239,6 @@ public class InputManagerService extends IInputManager.Stub
// Native callback.
@SuppressWarnings("unused")
- private int getPointerDisplayId() {
- synchronized (mAdditionalDisplayInputPropertiesLock) {
- // Prefer the override to all other displays.
- if (mOverriddenPointerDisplayId != Display.INVALID_DISPLAY) {
- return mOverriddenPointerDisplayId;
- }
- }
- return mWindowManagerCallbacks.getPointerDisplayId();
- }
-
- // Native callback.
- @SuppressWarnings("unused")
private String[] getKeyboardLayoutOverlay(InputDeviceIdentifier identifier) {
if (!mSystemReady) {
return null;
@@ -3206,6 +3277,26 @@ public class InputManagerService extends IInputManager.Stub
return null;
}
+ private static class PointerDisplayIdChangedArgs {
+ final int mPointerDisplayId;
+ final float mXPosition;
+ final float mYPosition;
+ PointerDisplayIdChangedArgs(int pointerDisplayId, float xPosition, float yPosition) {
+ mPointerDisplayId = pointerDisplayId;
+ mXPosition = xPosition;
+ mYPosition = yPosition;
+ }
+ }
+
+ // Native callback.
+ @SuppressWarnings("unused")
+ @VisibleForTesting
+ void onPointerDisplayIdChanged(int pointerDisplayId, float xPosition, float yPosition) {
+ mHandler.obtainMessage(MSG_POINTER_DISPLAY_ID_CHANGED,
+ new PointerDisplayIdChangedArgs(pointerDisplayId, xPosition,
+ yPosition)).sendToTarget();
+ }
+
/**
* Callback interface implemented by the Window Manager.
*/
@@ -3329,6 +3420,14 @@ public class InputManagerService extends IInputManager.Stub
*/
@Nullable
SurfaceControl createSurfaceForGestureMonitor(String name, int displayId);
+
+ /**
+ * Notify WindowManagerService when the display of the mouse pointer changes.
+ * @param displayId The display on which the mouse pointer is shown.
+ * @param x The x coordinate of the mouse pointer.
+ * @param y The y coordinate of the mouse pointer.
+ */
+ void notifyPointerDisplayIdChanged(int displayId, float x, float y);
}
/**
@@ -3381,6 +3480,9 @@ public class InputManagerService extends IInputManager.Stub
boolean inTabletMode = (boolean) args.arg1;
deliverTabletModeChanged(whenNanos, inTabletMode);
break;
+ case MSG_POINTER_DISPLAY_ID_CHANGED:
+ handlePointerDisplayIdChanged((PointerDisplayIdChangedArgs) msg.obj);
+ break;
}
}
}
@@ -3631,8 +3733,9 @@ public class InputManagerService extends IInputManager.Stub
}
@Override
- public void setVirtualMousePointerDisplayId(int pointerDisplayId) {
- InputManagerService.this.setVirtualMousePointerDisplayId(pointerDisplayId);
+ public boolean setVirtualMousePointerDisplayId(int pointerDisplayId) {
+ return InputManagerService.this
+ .setVirtualMousePointerDisplayIdBlocking(pointerDisplayId);
}
@Override
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 2169155343cd..81882d277a99 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -176,6 +176,9 @@ public interface NativeInputManagerService {
void cancelCurrentTouch();
+ /** Set the displayId on which the mouse cursor should be shown. */
+ void setPointerDisplayId(int displayId);
+
/** The native implementation of InputManagerService methods. */
class NativeImpl implements NativeInputManagerService {
/** Pointer to native input manager service object, used by native code. */
@@ -388,5 +391,8 @@ public interface NativeInputManagerService {
@Override
public native void cancelCurrentTouch();
+
+ @Override
+ public native void setPointerDisplayId(int displayId);
}
}
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
index db17c1056e39..8180e66166d9 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
@@ -64,7 +64,9 @@ final class HandwritingEventReceiverSurface {
| InputConfig.INTERCEPTS_STYLUS
| InputConfig.TRUSTED_OVERLAY;
- // The touchable region of this input surface is not initially configured.
+ // Configure the surface to receive stylus events across the entire display.
+ mWindowHandle.replaceTouchableRegionWithCrop(null /* use this surface's bounds */);
+
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
t.setInputWindowInfo(mInputSurface, mWindowHandle);
t.setLayer(mInputSurface, HANDWRITING_SURFACE_LAYER);
@@ -81,10 +83,6 @@ final class HandwritingEventReceiverSurface {
mWindowHandle.ownerUid = imeUid;
mWindowHandle.inputConfig &= ~InputConfig.SPY;
- // Update the touchable region so that the IME can intercept stylus events
- // across the entire display.
- mWindowHandle.replaceTouchableRegionWithCrop(null /* use this surface's bounds */);
-
new SurfaceControl.Transaction()
.setInputWindowInfo(mInputSurface, mWindowHandle)
.apply();
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
index a70677222506..f89b6aedf1f5 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
@@ -91,7 +91,7 @@ final class HandwritingModeController {
* InputEventReceiver that batches events according to the current thread's Choreographer.
*/
@UiThread
- void initializeHandwritingSpy(int displayId, IBinder focusedWindowToken) {
+ void initializeHandwritingSpy(int displayId) {
// When resetting, reuse resources if we are reinitializing on the same display.
reset(displayId == mCurrentDisplayId);
mCurrentDisplayId = displayId;
@@ -115,12 +115,6 @@ final class HandwritingModeController {
mHandwritingSurface = new HandwritingEventReceiverSurface(
name, displayId, surface, channel);
- // Configure the handwriting window to receive events over the focused window's bounds.
- mWindowManagerInternal.replaceInputSurfaceTouchableRegionWithWindowCrop(
- mHandwritingSurface.getSurface(),
- mHandwritingSurface.getInputWindowHandle(),
- focusedWindowToken);
-
// Use a dup of the input channel so that event processing can be paused by disposing the
// event receiver without causing a fd hangup.
mHandwritingEventReceiver = new BatchedInputEventReceiver.SimpleBatchedInputEventReceiver(
@@ -149,7 +143,8 @@ final class HandwritingModeController {
*/
@UiThread
@Nullable
- HandwritingSession startHandwritingSession(int requestId, int imePid, int imeUid) {
+ HandwritingSession startHandwritingSession(
+ int requestId, int imePid, int imeUid, IBinder focusedWindowToken) {
if (mHandwritingSurface == null) {
Slog.e(TAG, "Cannot start handwriting session: Handwriting was not initialized.");
return null;
@@ -158,12 +153,20 @@ final class HandwritingModeController {
Slog.e(TAG, "Cannot start handwriting session: Invalid request id: " + requestId);
return null;
}
- if (!mRecordingGesture) {
+ if (!mRecordingGesture || mHandwritingBuffer.isEmpty()) {
Slog.e(TAG, "Cannot start handwriting session: No stylus gesture is being recorded.");
return null;
}
Objects.requireNonNull(mHandwritingEventReceiver,
"Handwriting session was already transferred to IME.");
+ final MotionEvent downEvent = mHandwritingBuffer.get(0);
+ assert (downEvent.getActionMasked() == MotionEvent.ACTION_DOWN);
+ if (!mWindowManagerInternal.isPointInsideWindow(
+ focusedWindowToken, mCurrentDisplayId, downEvent.getRawX(), downEvent.getRawY())) {
+ Slog.e(TAG, "Cannot start handwriting session: "
+ + "Stylus gesture did not start inside the focused window.");
+ return null;
+ }
if (DEBUG) Slog.d(TAG, "Starting handwriting session in display: " + mCurrentDisplayId);
mInputManagerInternal.pilferPointers(mHandwritingSurface.getInputChannel().getToken());
@@ -226,13 +229,17 @@ final class HandwritingModeController {
}
if (!(ev instanceof MotionEvent)) {
- Slog.e("Stylus", "Received non-motion event in stylus monitor.");
+ Slog.wtf(TAG, "Received non-motion event in stylus monitor.");
return false;
}
final MotionEvent event = (MotionEvent) ev;
if (!isStylusEvent(event)) {
return false;
}
+ if (event.getDisplayId() != mCurrentDisplayId) {
+ Slog.wtf(TAG, "Received stylus event associated with the incorrect display.");
+ return false;
+ }
onStylusEvent(event);
return true;
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
index e62c5c13f04d..170331032086 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
@@ -30,6 +30,7 @@ import android.view.MotionEvent;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputMethodSubtype;
+import android.window.ImeOnBackInvokedDispatcher;
import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
import com.android.internal.inputmethod.InputMethodNavButtonFlags;
@@ -148,10 +149,11 @@ final class IInputMethodInvoker {
@AnyThread
void startInput(IBinder startInputToken, IInputContext inputContext, EditorInfo attribute,
- boolean restarting, @InputMethodNavButtonFlags int navButtonFlags) {
+ boolean restarting, @InputMethodNavButtonFlags int navButtonFlags,
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
try {
mTarget.startInput(startInputToken, inputContext, attribute, restarting,
- navButtonFlags);
+ navButtonFlags, imeDispatcher);
} catch (RemoteException e) {
logRemoteException(e);
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 6af00b3fbeea..ea2b1571c0ae 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -150,6 +150,7 @@ import android.view.inputmethod.InputMethodEditorTraceProto.InputMethodServiceTr
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
+import android.window.ImeOnBackInvokedDispatcher;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageMonitor;
@@ -616,6 +617,12 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
IInputContext mCurInputContext;
/**
+ * The {@link ImeOnBackInvokedDispatcher} last provided by the current client to
+ * receive {@link android.window.OnBackInvokedCallback}s forwarded from IME.
+ */
+ ImeOnBackInvokedDispatcher mCurImeDispatcher;
+
+ /**
* The {@link IRemoteAccessibilityInputConnection} last provided by the current client.
*/
@Nullable IRemoteAccessibilityInputConnection mCurRemoteAccessibilityInputConnection;
@@ -2623,7 +2630,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
final SessionState session = mCurClient.curSession;
setEnabledSessionLocked(session);
session.method.startInput(startInputToken, mCurInputContext, mCurAttribute, restarting,
- navButtonFlags);
+ navButtonFlags, mCurImeDispatcher);
if (mShowRequested) {
if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
showCurrentInputLocked(mCurFocusedWindow, getAppShowFlagsLocked(), null,
@@ -2733,7 +2740,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
@Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
@NonNull EditorInfo attribute, @StartInputFlags int startInputFlags,
@StartInputReason int startInputReason,
- int unverifiedTargetSdkVersion) {
+ int unverifiedTargetSdkVersion,
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
// If no method is currently selected, do nothing.
final String selectedMethodId = getSelectedMethodIdLocked();
if (selectedMethodId == null) {
@@ -2777,6 +2785,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
mCurClient = cs;
mCurInputContext = inputContext;
mCurRemoteAccessibilityInputConnection = remoteAccessibilityInputConnection;
+ mCurImeDispatcher = imeDispatcher;
mCurVirtualDisplayToScreenMatrix =
getVirtualDisplayToScreenMatrixLocked(cs.selfReportedDisplayId,
mDisplayIdToShowIme);
@@ -3780,10 +3789,12 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
@StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode,
int windowFlags, @Nullable EditorInfo attribute, IInputContext inputContext,
IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
- int unverifiedTargetSdkVersion) {
+ int unverifiedTargetSdkVersion,
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
return startInputOrWindowGainedFocusInternal(startInputReason, client, windowToken,
startInputFlags, softInputMode, windowFlags, attribute, inputContext,
- remoteAccessibilityInputConnection, unverifiedTargetSdkVersion);
+ remoteAccessibilityInputConnection, unverifiedTargetSdkVersion,
+ imeDispatcher);
}
@NonNull
@@ -3792,7 +3803,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
@StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode,
int windowFlags, @Nullable EditorInfo attribute, @Nullable IInputContext inputContext,
@Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
- int unverifiedTargetSdkVersion) {
+ int unverifiedTargetSdkVersion,
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
if (windowToken == null) {
Slog.e(TAG, "windowToken cannot be null.");
return InputBindResult.NULL;
@@ -3829,7 +3841,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
result = startInputOrWindowGainedFocusInternalLocked(startInputReason,
client, windowToken, startInputFlags, softInputMode, windowFlags,
attribute, inputContext, remoteAccessibilityInputConnection,
- unverifiedTargetSdkVersion, userId);
+ unverifiedTargetSdkVersion, userId, imeDispatcher);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -3857,7 +3869,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
@SoftInputModeFlags int softInputMode, int windowFlags, EditorInfo attribute,
IInputContext inputContext,
@Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
- int unverifiedTargetSdkVersion, @UserIdInt int userId) {
+ int unverifiedTargetSdkVersion, @UserIdInt int userId,
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
if (DEBUG) {
Slog.v(TAG, "startInputOrWindowGainedFocusInternalLocked: reason="
+ InputMethodDebug.startInputReasonToString(startInputReason)
@@ -3868,7 +3881,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
+ InputMethodDebug.startInputFlagsToString(startInputFlags)
+ " softInputMode=" + InputMethodDebug.softInputModeToString(softInputMode)
+ " windowFlags=#" + Integer.toHexString(windowFlags)
- + " unverifiedTargetSdkVersion=" + unverifiedTargetSdkVersion);
+ + " unverifiedTargetSdkVersion=" + unverifiedTargetSdkVersion
+ + " imeDispatcher=" + imeDispatcher);
}
final ClientState cs = mClients.get(client.asBinder());
@@ -3952,7 +3966,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
if (attribute != null) {
return startInputUncheckedLocked(cs, inputContext,
remoteAccessibilityInputConnection, attribute, startInputFlags,
- startInputReason, unverifiedTargetSdkVersion);
+ startInputReason, unverifiedTargetSdkVersion, imeDispatcher);
}
return new InputBindResult(
InputBindResult.ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY,
@@ -3993,7 +4007,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
if (isTextEditor && attribute != null
&& shouldRestoreImeVisibility(windowToken, softInputMode)) {
res = startInputUncheckedLocked(cs, inputContext, remoteAccessibilityInputConnection,
- attribute, startInputFlags, startInputReason, unverifiedTargetSdkVersion);
+ attribute, startInputFlags, startInputReason, unverifiedTargetSdkVersion,
+ imeDispatcher);
showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY);
return res;
@@ -4033,7 +4048,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
if (attribute != null) {
res = startInputUncheckedLocked(cs, inputContext,
remoteAccessibilityInputConnection, attribute, startInputFlags,
- startInputReason, unverifiedTargetSdkVersion);
+ startInputReason, unverifiedTargetSdkVersion,
+ imeDispatcher);
didStart = true;
}
showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
@@ -4065,7 +4081,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
if (attribute != null) {
res = startInputUncheckedLocked(cs, inputContext,
remoteAccessibilityInputConnection, attribute, startInputFlags,
- startInputReason, unverifiedTargetSdkVersion);
+ startInputReason, unverifiedTargetSdkVersion,
+ imeDispatcher);
didStart = true;
}
showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
@@ -4085,7 +4102,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
if (attribute != null) {
res = startInputUncheckedLocked(cs, inputContext,
remoteAccessibilityInputConnection, attribute, startInputFlags,
- startInputReason, unverifiedTargetSdkVersion);
+ startInputReason, unverifiedTargetSdkVersion,
+ imeDispatcher);
didStart = true;
}
showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
@@ -4115,7 +4133,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
res = startInputUncheckedLocked(cs, inputContext,
remoteAccessibilityInputConnection, attribute, startInputFlags,
- startInputReason, unverifiedTargetSdkVersion);
+ startInputReason, unverifiedTargetSdkVersion,
+ imeDispatcher);
} else {
res = InputBindResult.NULL_EDITOR_INFO;
}
@@ -5099,9 +5118,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
case MSG_RESET_HANDWRITING: {
synchronized (ImfLock.class) {
if (mBindingController.supportsStylusHandwriting()
- && getCurMethodLocked() != null && mCurFocusedWindow != null) {
- mHwController.initializeHandwritingSpy(
- mCurTokenDisplayId, mCurFocusedWindow);
+ && getCurMethodLocked() != null) {
+ mHwController.initializeHandwritingSpy(mCurTokenDisplayId);
} else {
mHwController.reset();
}
@@ -5111,14 +5129,15 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
case MSG_START_HANDWRITING:
synchronized (ImfLock.class) {
IInputMethodInvoker curMethod = getCurMethodLocked();
- if (curMethod == null) {
+ if (curMethod == null || mCurFocusedWindow == null) {
return true;
}
final HandwritingModeController.HandwritingSession session =
mHwController.startHandwritingSession(
msg.arg1 /*requestId*/,
msg.arg2 /*pid*/,
- mBindingController.getCurMethodUid());
+ mBindingController.getCurMethodUid(),
+ mCurFocusedWindow);
if (session == null) {
Slog.e(TAG,
"Failed to start handwriting session for requestId: " + msg.arg1);
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 92703ec86d06..604e8f3949f4 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -60,8 +60,8 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
import java.util.NoSuchElementException;
+import java.util.concurrent.CopyOnWriteArrayList;
/**
* This is the system implementation of a Session. Apps will interact with the
@@ -1159,6 +1159,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
public void sendCommand(String packageName, int pid, int uid, String command, Bundle args,
ResultReceiver cb) {
try {
+ final String reason = TAG + ":" + command;
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onCommand(packageName, pid, uid, command, args, cb);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in sendCommand.", e);
@@ -1168,6 +1171,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
public void sendCustomAction(String packageName, int pid, int uid, String action,
Bundle args) {
try {
+ final String reason = TAG + ":custom-" + action;
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onCustomAction(packageName, pid, uid, action, args);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in sendCustomAction.", e);
@@ -1176,6 +1182,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
public void prepare(String packageName, int pid, int uid) {
try {
+ final String reason = TAG + ":prepare";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onPrepare(packageName, pid, uid);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in prepare.", e);
@@ -1185,6 +1194,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
public void prepareFromMediaId(String packageName, int pid, int uid, String mediaId,
Bundle extras) {
try {
+ final String reason = TAG + ":prepareFromMediaId";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onPrepareFromMediaId(packageName, pid, uid, mediaId, extras);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in prepareFromMediaId.", e);
@@ -1194,6 +1206,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
public void prepareFromSearch(String packageName, int pid, int uid, String query,
Bundle extras) {
try {
+ final String reason = TAG + ":prepareFromSearch";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onPrepareFromSearch(packageName, pid, uid, query, extras);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in prepareFromSearch.", e);
@@ -1202,6 +1217,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
public void prepareFromUri(String packageName, int pid, int uid, Uri uri, Bundle extras) {
try {
+ final String reason = TAG + ":prepareFromUri";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onPrepareFromUri(packageName, pid, uid, uri, extras);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in prepareFromUri.", e);
@@ -1210,6 +1228,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
public void play(String packageName, int pid, int uid) {
try {
+ final String reason = TAG + ":play";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onPlay(packageName, pid, uid);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in play.", e);
@@ -1219,6 +1240,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
public void playFromMediaId(String packageName, int pid, int uid, String mediaId,
Bundle extras) {
try {
+ final String reason = TAG + ":playFromMediaId";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onPlayFromMediaId(packageName, pid, uid, mediaId, extras);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in playFromMediaId.", e);
@@ -1228,6 +1252,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
public void playFromSearch(String packageName, int pid, int uid, String query,
Bundle extras) {
try {
+ final String reason = TAG + ":playFromSearch";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onPlayFromSearch(packageName, pid, uid, query, extras);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in playFromSearch.", e);
@@ -1236,6 +1263,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
public void playFromUri(String packageName, int pid, int uid, Uri uri, Bundle extras) {
try {
+ final String reason = TAG + ":playFromUri";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onPlayFromUri(packageName, pid, uid, uri, extras);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in playFromUri.", e);
@@ -1244,6 +1274,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
public void skipToTrack(String packageName, int pid, int uid, long id) {
try {
+ final String reason = TAG + ":skipToTrack";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onSkipToTrack(packageName, pid, uid, id);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in skipToTrack", e);
@@ -1252,6 +1285,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
public void pause(String packageName, int pid, int uid) {
try {
+ final String reason = TAG + ":pause";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onPause(packageName, pid, uid);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in pause.", e);
@@ -1260,6 +1296,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
public void stop(String packageName, int pid, int uid) {
try {
+ final String reason = TAG + ":stop";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onStop(packageName, pid, uid);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in stop.", e);
@@ -1268,6 +1307,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
public void next(String packageName, int pid, int uid) {
try {
+ final String reason = TAG + ":next";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onNext(packageName, pid, uid);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in next.", e);
@@ -1276,6 +1318,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
public void previous(String packageName, int pid, int uid) {
try {
+ final String reason = TAG + ":previous";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onPrevious(packageName, pid, uid);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in previous.", e);
@@ -1284,6 +1329,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
public void fastForward(String packageName, int pid, int uid) {
try {
+ final String reason = TAG + ":fastForward";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onFastForward(packageName, pid, uid);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in fastForward.", e);
@@ -1292,6 +1340,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
public void rewind(String packageName, int pid, int uid) {
try {
+ final String reason = TAG + ":rewind";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onRewind(packageName, pid, uid);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in rewind.", e);
@@ -1300,6 +1351,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
public void seekTo(String packageName, int pid, int uid, long pos) {
try {
+ final String reason = TAG + ":seekTo";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onSeekTo(packageName, pid, uid, pos);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in seekTo.", e);
@@ -1308,6 +1362,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
public void rate(String packageName, int pid, int uid, Rating rating) {
try {
+ final String reason = TAG + ":rate";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onRate(packageName, pid, uid, rating);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in rate.", e);
@@ -1316,6 +1373,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
public void setPlaybackSpeed(String packageName, int pid, int uid, float speed) {
try {
+ final String reason = TAG + ":setPlaybackSpeed";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onSetPlaybackSpeed(packageName, pid, uid, speed);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in setPlaybackSpeed.", e);
@@ -1325,6 +1385,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
public void adjustVolume(String packageName, int pid, int uid, boolean asSystemService,
int direction) {
try {
+ final String reason = TAG + ":adjustVolume";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
if (asSystemService) {
mCb.onAdjustVolume(mContext.getPackageName(), Process.myPid(),
Process.SYSTEM_UID, direction);
@@ -1338,6 +1401,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
public void setVolumeTo(String packageName, int pid, int uid, int value) {
try {
+ final String reason = TAG + ":setVolumeTo";
+ mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
+ pid, uid, packageName, reason);
mCb.onSetVolumeTo(packageName, pid, uid, value);
} catch (RemoteException e) {
Log.e(TAG, "Remote failure in setVolumeTo.", e);
diff --git a/services/core/java/com/android/server/notification/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java
index 02f9ceb2d11d..89902f7f8321 100644
--- a/services/core/java/com/android/server/notification/NotificationDelegate.java
+++ b/services/core/java/com/android/server/notification/NotificationDelegate.java
@@ -51,14 +51,16 @@ public interface NotificationDelegate {
void onNotificationSettingsViewed(String key);
/**
* Called when the state of {@link Notification#FLAG_BUBBLE} is changed.
+ *
+ * @param key the notification key
+ * @param isBubble whether the notification should have {@link Notification#FLAG_BUBBLE} applied
+ * @param flags the flags to apply to the notification's {@link Notification.BubbleMetadata}
*/
void onNotificationBubbleChanged(String key, boolean isBubble, int flags);
/**
- * Called when the state of {@link Notification.BubbleMetadata#FLAG_SUPPRESS_NOTIFICATION}
- * or {@link Notification.BubbleMetadata#FLAG_SUPPRESS_BUBBLE} changes.
+ * Called when the flags on {@link Notification.BubbleMetadata} are changed.
*/
- void onBubbleNotificationSuppressionChanged(String key, boolean isNotifSuppressed,
- boolean isBubbleSuppressed);
+ void onBubbleMetadataFlagChanged(String key, int flags);
/**
* Grant permission to read the specified URI to the package associated with the
diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
index c548e7edc3cf..8a627367c1dc 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
@@ -42,4 +42,7 @@ public interface NotificationManagerInternal {
/** Does the specified package/uid have permission to post notifications? */
boolean areNotificationsEnabledForPackage(String pkg, int uid);
+
+ /** Send a notification to the user prompting them to review their notification permissions. */
+ void sendReviewPermissionsNotification();
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 6078bfc95488..83c576e9259d 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -18,6 +18,7 @@ package com.android.server.notification;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
import static android.app.Notification.FLAG_BUBBLE;
import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
@@ -274,6 +275,7 @@ import com.android.internal.logging.InstanceIdSequence;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.SomeArgs;
@@ -442,6 +444,18 @@ public class NotificationManagerService extends SystemService {
private static final int NOTIFICATION_INSTANCE_ID_MAX = (1 << 13);
+ // States for the review permissions notification
+ static final int REVIEW_NOTIF_STATE_UNKNOWN = -1;
+ static final int REVIEW_NOTIF_STATE_SHOULD_SHOW = 0;
+ static final int REVIEW_NOTIF_STATE_USER_INTERACTED = 1;
+ static final int REVIEW_NOTIF_STATE_DISMISSED = 2;
+ static final int REVIEW_NOTIF_STATE_RESHOWN = 3;
+
+ // Action strings for review permissions notification
+ static final String REVIEW_NOTIF_ACTION_REMIND = "REVIEW_NOTIF_ACTION_REMIND";
+ static final String REVIEW_NOTIF_ACTION_DISMISS = "REVIEW_NOTIF_ACTION_DISMISS";
+ static final String REVIEW_NOTIF_ACTION_CANCELED = "REVIEW_NOTIF_ACTION_CANCELED";
+
/**
* Apps that post custom toasts in the background will have those blocked. Apps can
* still post toasts created with
@@ -652,6 +666,9 @@ public class NotificationManagerService extends SystemService {
private InstanceIdSequence mNotificationInstanceIdSequence;
private Set<String> mMsgPkgsAllowedAsConvos = new HashSet();
+ // Broadcast intent receiver for notification permissions review-related intents
+ private ReviewNotificationPermissionsReceiver mReviewNotificationPermissionsReceiver;
+
static class Archive {
final SparseArray<Boolean> mEnabled;
final int mBufferSize;
@@ -1413,8 +1430,7 @@ public class NotificationManagerService extends SystemService {
}
@Override
- public void onBubbleNotificationSuppressionChanged(String key, boolean isNotifSuppressed,
- boolean isBubbleSuppressed) {
+ public void onBubbleMetadataFlagChanged(String key, int flags) {
synchronized (mNotificationLock) {
NotificationRecord r = mNotificationsByKey.get(key);
if (r != null) {
@@ -1424,17 +1440,12 @@ public class NotificationManagerService extends SystemService {
return;
}
- boolean flagChanged = false;
- if (data.isNotificationSuppressed() != isNotifSuppressed) {
- flagChanged = true;
- data.setSuppressNotification(isNotifSuppressed);
- }
- if (data.isBubbleSuppressed() != isBubbleSuppressed) {
- flagChanged = true;
- data.setSuppressBubble(isBubbleSuppressed);
- }
- if (flagChanged) {
+ if (flags != data.getFlags()) {
+ data.setFlags(flags);
+ // Shouldn't alert again just because of a flag change.
r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE;
+ // Force isAppForeground true here, because for sysui's purposes we
+ // want to be able to adjust the flag behaviour.
mHandler.post(
new EnqueueNotificationRunnable(r.getUser().getIdentifier(), r,
true /* isAppForeground */, SystemClock.elapsedRealtime()));
@@ -2416,6 +2427,11 @@ public class NotificationManagerService extends SystemService {
IntentFilter localeChangedFilter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED);
getContext().registerReceiver(mLocaleChangeReceiver, localeChangedFilter);
+
+ mReviewNotificationPermissionsReceiver = new ReviewNotificationPermissionsReceiver();
+ getContext().registerReceiver(mReviewNotificationPermissionsReceiver,
+ ReviewNotificationPermissionsReceiver.getFilter(),
+ Context.RECEIVER_NOT_EXPORTED);
}
/**
@@ -2709,6 +2725,7 @@ public class NotificationManagerService extends SystemService {
mHistoryManager.onBootPhaseAppsCanStart();
registerDeviceConfigChange();
migrateDefaultNAS();
+ maybeShowInitialReviewPermissionsNotification();
} else if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
mSnoozeHelper.scheduleRepostsForPersistedNotifications(System.currentTimeMillis());
}
@@ -6336,6 +6353,21 @@ public class NotificationManagerService extends SystemService {
public boolean areNotificationsEnabledForPackage(String pkg, int uid) {
return areNotificationsEnabledForPackageInt(pkg, uid);
}
+
+ @Override
+ public void sendReviewPermissionsNotification() {
+ // This method is meant to be called from the JobService upon running the job for this
+ // notification having been rescheduled; so without checking any other state, it will
+ // send the notification.
+ checkCallerIsSystem();
+ NotificationManager nm = getContext().getSystemService(NotificationManager.class);
+ nm.notify(TAG,
+ SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS,
+ createReviewPermissionsNotification());
+ Settings.Global.putInt(getContext().getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN);
+ }
};
int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted) {
@@ -7145,10 +7177,12 @@ public class NotificationManagerService extends SystemService {
&& r.getNotification().isBubbleNotification())
|| (mReason == REASON_CLICK && r.canBubble()
&& r.isFlagBubbleRemoved())) {
- boolean isBubbleSuppressed = r.getNotification().getBubbleMetadata() != null
- && r.getNotification().getBubbleMetadata().isBubbleSuppressed();
- mNotificationDelegate.onBubbleNotificationSuppressionChanged(
- r.getKey(), true /* notifSuppressed */, isBubbleSuppressed);
+ int flags = 0;
+ if (r.getNotification().getBubbleMetadata() != null) {
+ flags = r.getNotification().getBubbleMetadata().getFlags();
+ }
+ flags |= FLAG_SUPPRESS_NOTIFICATION;
+ mNotificationDelegate.onBubbleMetadataFlagChanged(r.getKey(), flags);
return;
}
if ((r.getNotification().flags & mMustHaveFlags) != mMustHaveFlags) {
@@ -11608,6 +11642,76 @@ public class NotificationManagerService extends SystemService {
out.endTag(null, LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_TAG);
}
+ // Creates a notification that informs the user about changes due to the migration to
+ // use permissions for notifications.
+ protected Notification createReviewPermissionsNotification() {
+ int title = R.string.review_notification_settings_title;
+ int content = R.string.review_notification_settings_text;
+
+ // Tapping on the notification leads to the settings screen for managing app notifications,
+ // using the intent reserved for system services to indicate it comes from this notification
+ Intent tapIntent = new Intent(Settings.ACTION_ALL_APPS_NOTIFICATION_SETTINGS_FOR_REVIEW);
+ Intent remindIntent = new Intent(REVIEW_NOTIF_ACTION_REMIND);
+ Intent dismissIntent = new Intent(REVIEW_NOTIF_ACTION_DISMISS);
+ Intent swipeIntent = new Intent(REVIEW_NOTIF_ACTION_CANCELED);
+
+ // Both "remind me" and "dismiss" actions will be actions received by the BroadcastReceiver
+ final Notification.Action remindMe = new Notification.Action.Builder(null,
+ getContext().getResources().getString(
+ R.string.review_notification_settings_remind_me_action),
+ PendingIntent.getBroadcast(
+ getContext(), 0, remindIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
+ .build();
+ final Notification.Action dismiss = new Notification.Action.Builder(null,
+ getContext().getResources().getString(
+ R.string.review_notification_settings_dismiss),
+ PendingIntent.getBroadcast(
+ getContext(), 0, dismissIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
+ .build();
+
+ return new Notification.Builder(getContext(), SystemNotificationChannels.SYSTEM_CHANGES)
+ .setSmallIcon(R.drawable.stat_sys_adb)
+ .setContentTitle(getContext().getResources().getString(title))
+ .setContentText(getContext().getResources().getString(content))
+ .setContentIntent(PendingIntent.getActivity(getContext(), 0, tapIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
+ .setStyle(new Notification.BigTextStyle())
+ .setFlag(Notification.FLAG_NO_CLEAR, true)
+ .setAutoCancel(true)
+ .addAction(remindMe)
+ .addAction(dismiss)
+ .setDeleteIntent(PendingIntent.getBroadcast(getContext(), 0, swipeIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
+ .build();
+ }
+
+ protected void maybeShowInitialReviewPermissionsNotification() {
+ int currentState = Settings.Global.getInt(getContext().getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ REVIEW_NOTIF_STATE_UNKNOWN);
+
+ // now check the last known state of the notification -- this determination of whether the
+ // user is in the correct target audience occurs elsewhere, and will have written the
+ // REVIEW_NOTIF_STATE_SHOULD_SHOW to indicate it should be shown in the future.
+ //
+ // alternatively, if the user has rescheduled the notification (so it has been shown
+ // again) but not yet interacted with the new notification, then show it again on boot,
+ // as this state indicates that the user had the notification open before rebooting.
+ //
+ // sending the notification here does not record a new state for the notification;
+ // that will be written by parts of the system further down the line if at any point
+ // the user interacts with the notification.
+ if (currentState == REVIEW_NOTIF_STATE_SHOULD_SHOW
+ || currentState == REVIEW_NOTIF_STATE_RESHOWN) {
+ NotificationManager nm = getContext().getSystemService(NotificationManager.class);
+ nm.notify(TAG,
+ SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS,
+ createReviewPermissionsNotification());
+ }
+ }
+
/**
* Shows a warning on logcat. Shows the toast only once per package. This is to avoid being too
* aggressive and annoying the user.
diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java
index a09aa7cea0a4..b4230c11bcab 100644
--- a/services/core/java/com/android/server/notification/PermissionHelper.java
+++ b/services/core/java/com/android/server/notification/PermissionHelper.java
@@ -55,10 +55,8 @@ public final class PermissionHelper {
private final PermissionManagerServiceInternal mPmi;
private final IPackageManager mPackageManager;
private final IPermissionManager mPermManager;
- // TODO (b/194833441): Remove this boolean (but keep the isMigrationEnabled() method)
- // when the migration is enabled
+ // TODO (b/194833441): Remove when the migration is enabled
private final boolean mMigrationEnabled;
- private final boolean mIsTv;
private final boolean mForceUserSetOnUpgrade;
public PermissionHelper(PermissionManagerServiceInternal pmi, IPackageManager packageManager,
@@ -69,17 +67,10 @@ public final class PermissionHelper {
mPermManager = permManager;
mMigrationEnabled = migrationEnabled;
mForceUserSetOnUpgrade = forceUserSetOnUpgrade;
- boolean isTv;
- try {
- isTv = mPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK, 0);
- } catch (RemoteException e) {
- isTv = false;
- }
- mIsTv = isTv;
}
public boolean isMigrationEnabled() {
- return mMigrationEnabled && !mIsTv;
+ return mMigrationEnabled;
}
/**
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 0525b1e33267..ef3c770f125b 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -96,6 +96,10 @@ public class PreferencesHelper implements RankingConfig {
private final int XML_VERSION;
/** What version to check to do the upgrade for bubbles. */
private static final int XML_VERSION_BUBBLES_UPGRADE = 1;
+ /** The first xml version with notification permissions enabled. */
+ private static final int XML_VERSION_NOTIF_PERMISSION = 3;
+ /** The first xml version that notifies users to review their notification permissions */
+ private static final int XML_VERSION_REVIEW_PERMISSIONS_NOTIFICATION = 4;
@VisibleForTesting
static final int UNKNOWN_UID = UserHandle.USER_NULL;
private static final String NON_BLOCKABLE_CHANNEL_DELIM = ":";
@@ -206,7 +210,7 @@ public class PreferencesHelper implements RankingConfig {
mStatsEventBuilderFactory = statsEventBuilderFactory;
if (mPermissionHelper.isMigrationEnabled()) {
- XML_VERSION = 3;
+ XML_VERSION = 4;
} else {
XML_VERSION = 2;
}
@@ -226,8 +230,16 @@ public class PreferencesHelper implements RankingConfig {
final int xmlVersion = parser.getAttributeInt(null, ATT_VERSION, -1);
boolean upgradeForBubbles = xmlVersion == XML_VERSION_BUBBLES_UPGRADE;
- boolean migrateToPermission =
- (xmlVersion < XML_VERSION) && mPermissionHelper.isMigrationEnabled();
+ boolean migrateToPermission = (xmlVersion < XML_VERSION_NOTIF_PERMISSION)
+ && mPermissionHelper.isMigrationEnabled();
+ if (xmlVersion < XML_VERSION_REVIEW_PERMISSIONS_NOTIFICATION) {
+ // make a note that we should show the notification at some point.
+ // it shouldn't be possible for the user to already have seen it, as the XML version
+ // would be newer then.
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW);
+ }
ArrayList<PermissionHelper.PackagePermission> pkgPerms = new ArrayList<>();
synchronized (mPackagePreferences) {
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
diff --git a/services/core/java/com/android/server/notification/ReviewNotificationPermissionsJobService.java b/services/core/java/com/android/server/notification/ReviewNotificationPermissionsJobService.java
new file mode 100644
index 000000000000..fde45f71a844
--- /dev/null
+++ b/services/core/java/com/android/server/notification/ReviewNotificationPermissionsJobService.java
@@ -0,0 +1,79 @@
+/*
+ * 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.notification;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+
+/**
+ * JobService implementation for scheduling the notification informing users about
+ * notification permissions updates and taking them to review their existing permissions.
+ * @hide
+ */
+public class ReviewNotificationPermissionsJobService extends JobService {
+ public static final String TAG = "ReviewNotificationPermissionsJobService";
+
+ @VisibleForTesting
+ protected static final int JOB_ID = 225373531;
+
+ /**
+ * Schedule a new job that will show a notification the specified amount of time in the future.
+ */
+ public static void scheduleJob(Context context, long rescheduleTimeMillis) {
+ JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+ // if the job already exists for some reason, cancel & reschedule
+ if (jobScheduler.getPendingJob(JOB_ID) != null) {
+ jobScheduler.cancel(JOB_ID);
+ }
+ ComponentName component = new ComponentName(
+ context, ReviewNotificationPermissionsJobService.class);
+ JobInfo newJob = new JobInfo.Builder(JOB_ID, component)
+ .setPersisted(true) // make sure it'll still get rescheduled after reboot
+ .setMinimumLatency(rescheduleTimeMillis) // run after specified amount of time
+ .build();
+ jobScheduler.schedule(newJob);
+ }
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ // While jobs typically should be run on different threads, this
+ // job only posts a notification, which is not a long-running operation
+ // as notification posting is asynchronous.
+ NotificationManagerInternal nmi =
+ LocalServices.getService(NotificationManagerInternal.class);
+ nmi.sendReviewPermissionsNotification();
+
+ // once the notification is posted, the job is done, so no need to
+ // keep it alive afterwards
+ return false;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ // If we're interrupted for some reason, try again (though this may not
+ // ever happen due to onStartJob not leaving a job running after being
+ // called)
+ return true;
+ }
+}
diff --git a/services/core/java/com/android/server/notification/ReviewNotificationPermissionsReceiver.java b/services/core/java/com/android/server/notification/ReviewNotificationPermissionsReceiver.java
new file mode 100644
index 000000000000..b99aeac44025
--- /dev/null
+++ b/services/core/java/com/android/server/notification/ReviewNotificationPermissionsReceiver.java
@@ -0,0 +1,122 @@
+/*
+ * 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.notification;
+
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.provider.Settings;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.messages.nano.SystemMessageProto;
+
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.temporal.ChronoUnit;
+
+/**
+ * Broadcast receiver for intents that come from the "review notification permissions" notification,
+ * shown to users who upgrade to T from an earlier OS to inform them of notification setup changes
+ * and invite them to review their notification permissions.
+ */
+public class ReviewNotificationPermissionsReceiver extends BroadcastReceiver {
+ public static final String TAG = "ReviewNotifPermissions";
+ static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ // 7 days in millis, as the amount of time to wait before re-sending the notification
+ private static final long JOB_RESCHEDULE_TIME = 1000 /* millis */ * 60 /* seconds */
+ * 60 /* minutes */ * 24 /* hours */ * 7 /* days */;
+
+ static IntentFilter getFilter() {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(NotificationManagerService.REVIEW_NOTIF_ACTION_REMIND);
+ filter.addAction(NotificationManagerService.REVIEW_NOTIF_ACTION_DISMISS);
+ filter.addAction(NotificationManagerService.REVIEW_NOTIF_ACTION_CANCELED);
+ return filter;
+ }
+
+ // Cancels the "review notification permissions" notification.
+ @VisibleForTesting
+ protected void cancelNotification(Context context) {
+ NotificationManager nm = context.getSystemService(NotificationManager.class);
+ if (nm != null) {
+ nm.cancel(NotificationManagerService.TAG,
+ SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS);
+ } else {
+ Slog.w(TAG, "could not cancel notification: NotificationManager not found");
+ }
+ }
+
+ @VisibleForTesting
+ protected void rescheduleNotification(Context context) {
+ ReviewNotificationPermissionsJobService.scheduleJob(context, JOB_RESCHEDULE_TIME);
+ // log if needed
+ if (DEBUG) {
+ Slog.d(TAG, "Scheduled review permissions notification for on or after: "
+ + LocalDateTime.now(ZoneId.systemDefault())
+ .plus(JOB_RESCHEDULE_TIME, ChronoUnit.MILLIS));
+ }
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(NotificationManagerService.REVIEW_NOTIF_ACTION_REMIND)) {
+ // Reschedule the notification for 7 days in the future
+ rescheduleNotification(context);
+
+ // note that the user has interacted; no longer needed to show the initial
+ // notification
+ Settings.Global.putInt(context.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED);
+ cancelNotification(context);
+ } else if (action.equals(NotificationManagerService.REVIEW_NOTIF_ACTION_DISMISS)) {
+ // user dismissed; write to settings so we don't show ever again
+ Settings.Global.putInt(context.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_DISMISSED);
+ cancelNotification(context);
+ } else if (action.equals(NotificationManagerService.REVIEW_NOTIF_ACTION_CANCELED)) {
+ // we may get here from the user swiping away the notification,
+ // or from the notification being canceled in any other way.
+ // only in the case that the user hasn't interacted with it in
+ // any other way yet, reschedule
+ int notifState = Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ /* default */ NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN);
+ if (notifState == NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW) {
+ // user hasn't interacted in the past, so reschedule once and then note that the
+ // user *has* interacted now so we don't re-reschedule if they swipe again
+ rescheduleNotification(context);
+ Settings.Global.putInt(context.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED);
+ } else if (notifState == NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN) {
+ // swiping away on a rescheduled notification; mark as interacted and
+ // don't reschedule again.
+ Settings.Global.putInt(context.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/ApkChecksums.java b/services/core/java/com/android/server/pm/ApkChecksums.java
index 2824585bf5aa..39119948fa3e 100644
--- a/services/core/java/com/android/server/pm/ApkChecksums.java
+++ b/services/core/java/com/android/server/pm/ApkChecksums.java
@@ -34,13 +34,14 @@ import android.content.Context;
import android.content.pm.ApkChecksum;
import android.content.pm.Checksum;
import android.content.pm.IOnChecksumsReadyListener;
-import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.Signature;
import android.content.pm.SigningDetails.SignatureSchemeVersion;
import android.content.pm.parsing.ApkLiteParseUtils;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
+import android.os.Environment;
+import android.os.FileUtils;
import android.os.Handler;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -63,7 +64,6 @@ import android.util.apk.VerityBuilder;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.security.VerityUtils;
-import com.android.server.LocalServices;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import java.io.ByteArrayOutputStream;
@@ -637,9 +637,18 @@ public class ApkChecksums {
return null;
}
+ private static boolean containsFile(File dir, String filePath) {
+ if (dir == null) {
+ return false;
+ }
+ return FileUtils.contains(dir.getAbsolutePath(), filePath);
+ }
+
private static ApkChecksum extractHashFromFS(String split, String filePath) {
// verity first
- {
+ // Skip /product folder.
+ // TODO(b/231354111): remove this hack once we are allowed to change SELinux rules.
+ if (!containsFile(Environment.getProductDirectory(), filePath)) {
byte[] hash = VerityUtils.getFsverityRootHash(filePath);
if (hash != null) {
return new ApkChecksum(split, TYPE_WHOLE_MERKLE_ROOT_4K_SHA256, hash);
diff --git a/services/core/java/com/android/server/pm/AppIdSettingMap.java b/services/core/java/com/android/server/pm/AppIdSettingMap.java
index b41a0b8878e0..20c67f87d3b9 100644
--- a/services/core/java/com/android/server/pm/AppIdSettingMap.java
+++ b/services/core/java/com/android/server/pm/AppIdSettingMap.java
@@ -19,6 +19,7 @@ package com.android.server.pm;
import android.os.Process;
import android.util.Log;
+import com.android.server.utils.SnapshotCache;
import com.android.server.utils.WatchedArrayList;
import com.android.server.utils.WatchedSparseArray;
import com.android.server.utils.Watcher;
@@ -34,10 +35,28 @@ final class AppIdSettingMap {
* index to the corresponding SettingBase object is (appId - FIRST_APPLICATION_ID). If an app ID
* doesn't exist (i.e., app is not installed), we fill the corresponding entry with null.
*/
- private WatchedArrayList<SettingBase> mNonSystemSettings = new WatchedArrayList<>();
- private WatchedSparseArray<SettingBase> mSystemSettings = new WatchedSparseArray<>();
+ private final WatchedArrayList<SettingBase> mNonSystemSettings;
+ private final SnapshotCache<WatchedArrayList<SettingBase>> mNonSystemSettingsSnapshot;
+ private final WatchedSparseArray<SettingBase> mSystemSettings;
+ private final SnapshotCache<WatchedSparseArray<SettingBase>> mSystemSettingsSnapshot;
private int mFirstAvailableAppId = Process.FIRST_APPLICATION_UID;
+ AppIdSettingMap() {
+ mNonSystemSettings = new WatchedArrayList<>();
+ mNonSystemSettingsSnapshot = new SnapshotCache.Auto<>(
+ mNonSystemSettings, mNonSystemSettings, "AppIdSettingMap.mNonSystemSettings");
+ mSystemSettings = new WatchedSparseArray<>();
+ mSystemSettingsSnapshot = new SnapshotCache.Auto<>(
+ mSystemSettings, mSystemSettings, "AppIdSettingMap.mSystemSettings");
+ }
+
+ AppIdSettingMap(AppIdSettingMap orig) {
+ mNonSystemSettings = orig.mNonSystemSettingsSnapshot.snapshot();
+ mNonSystemSettingsSnapshot = new SnapshotCache.Sealed<>();
+ mSystemSettings = orig.mSystemSettingsSnapshot.snapshot();
+ mSystemSettingsSnapshot = new SnapshotCache.Sealed<>();
+ }
+
/** Returns true if the requested AppID was valid and not already registered. */
public boolean registerExistingAppId(int appId, SettingBase setting, Object name) {
if (appId >= Process.FIRST_APPLICATION_UID) {
@@ -134,10 +153,7 @@ final class AppIdSettingMap {
}
public AppIdSettingMap snapshot() {
- AppIdSettingMap l = new AppIdSettingMap();
- mNonSystemSettings.snapshot(l.mNonSystemSettings, mNonSystemSettings);
- mSystemSettings.snapshot(l.mSystemSettings, mSystemSettings);
- return l;
+ return new AppIdSettingMap(this);
}
public void registerObserver(Watcher observer) {
diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
index 89f8be27096a..daac7c04098a 100644
--- a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
+++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
@@ -236,13 +236,6 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub {
verifyActivityCanHandleIntent(launchIntent, callingUid, userId);
- // Always show the cross profile animation
- if (options == null) {
- options = ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle();
- } else {
- options.putAll(ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle());
- }
-
mInjector.getActivityTaskManagerInternal()
.startActivityAsUser(
caller,
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 320b06f6dc3e..32f0f109821d 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -119,7 +119,7 @@ public class Installer extends SystemService {
IInstalld.FLAG_CLEAR_APP_DATA_KEEP_ART_PROFILES;
private final boolean mIsolated;
-
+ private volatile boolean mDeferSetFirstBoot;
private volatile IInstalld mInstalld;
private volatile Object mWarnIfHeld;
@@ -171,6 +171,7 @@ public class Installer extends SystemService {
mInstalld = IInstalld.Stub.asInterface(binder);
try {
invalidateMounts();
+ executeDeferredActions();
} catch (InstallerException ignored) {
}
} else {
@@ -180,6 +181,15 @@ public class Installer extends SystemService {
}
/**
+ * Perform any deferred actions on mInstalld while the connection could not be made.
+ */
+ private void executeDeferredActions() throws InstallerException {
+ if (mDeferSetFirstBoot) {
+ setFirstBoot();
+ }
+ }
+
+ /**
* Do several pre-flight checks before making a remote call.
*
* @return if the remote call should continue.
@@ -291,8 +301,15 @@ public class Installer extends SystemService {
return;
}
try {
- mInstalld.setFirstBoot();
- } catch (RemoteException e) {
+ // mInstalld might be null if the connection could not be established.
+ if (mInstalld != null) {
+ mInstalld.setFirstBoot();
+ } else {
+ // if it is null while trying to set the first boot, set a flag to try and set the
+ // first boot when the connection is eventually established
+ mDeferSetFirstBoot = true;
+ }
+ } catch (Exception e) {
throw InstallerException.from(e);
}
}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index ba4d09f28d05..6400502f1a89 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4579,7 +4579,7 @@ public final class Settings implements Watchable, Snappable {
pw.print(" (override=true)");
}
pw.println();
- if (ps.getPkg().getQueriesPackages().isEmpty()) {
+ if (!ps.getPkg().getQueriesPackages().isEmpty()) {
pw.append(prefix).append(" queriesPackages=")
.println(ps.getPkg().getQueriesPackages());
}
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index fef6ce1f67b3..fa0c6c393cdc 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -166,18 +166,19 @@ class ShortcutPackage extends ShortcutPackageItem {
* An in-memory copy of shortcuts for this package that was loaded from xml, keyed on IDs.
*/
@GuardedBy("mLock")
- final ArrayMap<String, ShortcutInfo> mShortcuts = new ArrayMap<>();
+ private final ArrayMap<String, ShortcutInfo> mShortcuts = new ArrayMap<>();
/**
* A temporary copy of shortcuts that are to be cleared once persisted into AppSearch, keyed on
* IDs.
*/
@GuardedBy("mLock")
- private ArrayMap<String, ShortcutInfo> mTransientShortcuts = new ArrayMap<>(0);
+ private final ArrayMap<String, ShortcutInfo> mTransientShortcuts = new ArrayMap<>(0);
/**
* All the share targets from the package
*/
+ @GuardedBy("mLock")
private final ArrayList<ShareTargetInfo> mShareTargets = new ArrayList<>(0);
/**
@@ -231,7 +232,9 @@ class ShortcutPackage extends ShortcutPackageItem {
}
public int getShortcutCount() {
- return mShortcuts.size();
+ synchronized (mLock) {
+ return mShortcuts.size();
+ }
}
@Override
@@ -272,7 +275,9 @@ class ShortcutPackage extends ShortcutPackageItem {
@Nullable
public ShortcutInfo findShortcutById(@Nullable final String id) {
if (id == null) return null;
- return mShortcuts.get(id);
+ synchronized (mLock) {
+ return mShortcuts.get(id);
+ }
}
public boolean isShortcutExistsAndInvisibleToPublisher(String id) {
@@ -347,11 +352,14 @@ class ShortcutPackage extends ShortcutPackageItem {
* Delete a shortcut by ID. This will *always* remove it even if it's immutable or invisible.
*/
private ShortcutInfo forceDeleteShortcutInner(@NonNull String id) {
- final ShortcutInfo shortcut = mShortcuts.remove(id);
- if (shortcut != null) {
- mShortcutUser.mService.removeIconLocked(shortcut);
- shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED
- | ShortcutInfo.FLAG_MANIFEST | ShortcutInfo.FLAG_CACHED_ALL);
+ final ShortcutInfo shortcut;
+ synchronized (mLock) {
+ shortcut = mShortcuts.remove(id);
+ if (shortcut != null) {
+ removeIcon(shortcut);
+ shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED
+ | ShortcutInfo.FLAG_MANIFEST | ShortcutInfo.FLAG_CACHED_ALL);
+ }
}
return shortcut;
}
@@ -366,7 +374,7 @@ class ShortcutPackage extends ShortcutPackageItem {
forceDeleteShortcutInner(newShortcut.getId());
// Extract Icon and update the icon res ID and the bitmap path.
- s.saveIconAndFixUpShortcutLocked(newShortcut);
+ s.saveIconAndFixUpShortcutLocked(this, newShortcut);
s.fixUpShortcutResourceNamesAndValues(newShortcut);
saveShortcut(newShortcut);
}
@@ -524,14 +532,16 @@ class ShortcutPackage extends ShortcutPackageItem {
public List<ShortcutInfo> deleteAllDynamicShortcuts() {
final long now = mShortcutUser.mService.injectCurrentTimeMillis();
boolean changed = false;
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- ShortcutInfo si = mShortcuts.valueAt(i);
- if (si.isDynamic() && si.isVisibleToPublisher()) {
- changed = true;
-
- si.setTimestamp(now);
- si.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
- si.setRank(0); // It may still be pinned, so clear the rank.
+ synchronized (mLock) {
+ for (int i = mShortcuts.size() - 1; i >= 0; i--) {
+ ShortcutInfo si = mShortcuts.valueAt(i);
+ if (si.isDynamic() && si.isVisibleToPublisher()) {
+ changed = true;
+
+ si.setTimestamp(now);
+ si.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
+ si.setRank(0); // It may still be pinned, so clear the rank.
+ }
}
}
removeAllShortcutsAsync();
@@ -874,59 +884,63 @@ class ShortcutPackage extends ShortcutPackageItem {
*/
public List<ShortcutManager.ShareShortcutInfo> getMatchingShareTargets(
@NonNull IntentFilter filter) {
- final List<ShareTargetInfo> matchedTargets = new ArrayList<>();
- for (int i = 0; i < mShareTargets.size(); i++) {
- final ShareTargetInfo target = mShareTargets.get(i);
- for (ShareTargetInfo.TargetData data : target.mTargetData) {
- if (filter.hasDataType(data.mMimeType)) {
- // Matched at least with one data type
- matchedTargets.add(target);
- break;
+ synchronized (mLock) {
+ final List<ShareTargetInfo> matchedTargets = new ArrayList<>();
+ for (int i = 0; i < mShareTargets.size(); i++) {
+ final ShareTargetInfo target = mShareTargets.get(i);
+ for (ShareTargetInfo.TargetData data : target.mTargetData) {
+ if (filter.hasDataType(data.mMimeType)) {
+ // Matched at least with one data type
+ matchedTargets.add(target);
+ break;
+ }
}
}
- }
- if (matchedTargets.isEmpty()) {
- return new ArrayList<>();
- }
+ if (matchedTargets.isEmpty()) {
+ return new ArrayList<>();
+ }
- // Get the list of all dynamic shortcuts in this package.
- final ArrayList<ShortcutInfo> shortcuts = new ArrayList<>();
- // Pass callingLauncher to ensure pinned flag marked by system ui, e.g. ShareSheet, are
- // included in the result
- findAll(shortcuts, ShortcutInfo::isNonManifestVisible,
- ShortcutInfo.CLONE_REMOVE_FOR_APP_PREDICTION,
- mShortcutUser.mService.mContext.getPackageName(),
- 0, /*getPinnedByAnyLauncher=*/ false);
+ // Get the list of all dynamic shortcuts in this package.
+ final ArrayList<ShortcutInfo> shortcuts = new ArrayList<>();
+ // Pass callingLauncher to ensure pinned flag marked by system ui, e.g. ShareSheet, are
+ // included in the result
+ findAll(shortcuts, ShortcutInfo::isNonManifestVisible,
+ ShortcutInfo.CLONE_REMOVE_FOR_APP_PREDICTION,
+ mShortcutUser.mService.mContext.getPackageName(),
+ 0, /*getPinnedByAnyLauncher=*/ false);
- final List<ShortcutManager.ShareShortcutInfo> result = new ArrayList<>();
- for (int i = 0; i < shortcuts.size(); i++) {
- final Set<String> categories = shortcuts.get(i).getCategories();
- if (categories == null || categories.isEmpty()) {
- continue;
- }
- for (int j = 0; j < matchedTargets.size(); j++) {
- // Shortcut must have all of share target categories
- boolean hasAllCategories = true;
- final ShareTargetInfo target = matchedTargets.get(j);
- for (int q = 0; q < target.mCategories.length; q++) {
- if (!categories.contains(target.mCategories[q])) {
- hasAllCategories = false;
+ final List<ShortcutManager.ShareShortcutInfo> result = new ArrayList<>();
+ for (int i = 0; i < shortcuts.size(); i++) {
+ final Set<String> categories = shortcuts.get(i).getCategories();
+ if (categories == null || categories.isEmpty()) {
+ continue;
+ }
+ for (int j = 0; j < matchedTargets.size(); j++) {
+ // Shortcut must have all of share target categories
+ boolean hasAllCategories = true;
+ final ShareTargetInfo target = matchedTargets.get(j);
+ for (int q = 0; q < target.mCategories.length; q++) {
+ if (!categories.contains(target.mCategories[q])) {
+ hasAllCategories = false;
+ break;
+ }
+ }
+ if (hasAllCategories) {
+ result.add(new ShortcutManager.ShareShortcutInfo(shortcuts.get(i),
+ new ComponentName(getPackageName(), target.mTargetClass)));
break;
}
}
- if (hasAllCategories) {
- result.add(new ShortcutManager.ShareShortcutInfo(shortcuts.get(i),
- new ComponentName(getPackageName(), target.mTargetClass)));
- break;
- }
}
+ return result;
}
- return result;
}
public boolean hasShareTargets() {
- return !mShareTargets.isEmpty();
+ synchronized (mLock) {
+ return !mShareTargets.isEmpty();
+ }
}
/**
@@ -935,44 +949,47 @@ class ShortcutPackage extends ShortcutPackageItem {
* the app's Xml resource.
*/
int getSharingShortcutCount() {
- if (mShareTargets.isEmpty()) {
- return 0;
- }
+ synchronized (mLock) {
+ if (mShareTargets.isEmpty()) {
+ return 0;
+ }
- // Get the list of all dynamic shortcuts in this package
- final ArrayList<ShortcutInfo> shortcuts = new ArrayList<>();
- findAll(shortcuts, ShortcutInfo::isNonManifestVisible,
- ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
+ // Get the list of all dynamic shortcuts in this package
+ final ArrayList<ShortcutInfo> shortcuts = new ArrayList<>();
+ findAll(shortcuts, ShortcutInfo::isNonManifestVisible,
+ ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
- int sharingShortcutCount = 0;
- for (int i = 0; i < shortcuts.size(); i++) {
- final Set<String> categories = shortcuts.get(i).getCategories();
- if (categories == null || categories.isEmpty()) {
- continue;
- }
- for (int j = 0; j < mShareTargets.size(); j++) {
- // A SharingShortcut must have all of share target categories
- boolean hasAllCategories = true;
- final ShareTargetInfo target = mShareTargets.get(j);
- for (int q = 0; q < target.mCategories.length; q++) {
- if (!categories.contains(target.mCategories[q])) {
- hasAllCategories = false;
+ int sharingShortcutCount = 0;
+ for (int i = 0; i < shortcuts.size(); i++) {
+ final Set<String> categories = shortcuts.get(i).getCategories();
+ if (categories == null || categories.isEmpty()) {
+ continue;
+ }
+ for (int j = 0; j < mShareTargets.size(); j++) {
+ // A SharingShortcut must have all of share target categories
+ boolean hasAllCategories = true;
+ final ShareTargetInfo target = mShareTargets.get(j);
+ for (int q = 0; q < target.mCategories.length; q++) {
+ if (!categories.contains(target.mCategories[q])) {
+ hasAllCategories = false;
+ break;
+ }
+ }
+ if (hasAllCategories) {
+ sharingShortcutCount++;
break;
}
}
- if (hasAllCategories) {
- sharingShortcutCount++;
- break;
- }
}
+ return sharingShortcutCount;
}
- return sharingShortcutCount;
}
/**
* Return the filenames (excluding path names) of icon bitmap files from this package.
*/
- public ArraySet<String> getUsedBitmapFiles() {
+ @GuardedBy("mLock")
+ private ArraySet<String> getUsedBitmapFilesLocked() {
final ArraySet<String> usedFiles = new ArraySet<>(1);
forEachShortcut(si -> {
if (si.getBitmapPath() != null) {
@@ -982,6 +999,26 @@ class ShortcutPackage extends ShortcutPackageItem {
return usedFiles;
}
+ public void cleanupDanglingBitmapFiles(@NonNull File path) {
+ synchronized (mLock) {
+ mShortcutBitmapSaver.waitForAllSavesLocked();
+ final ArraySet<String> usedFiles = getUsedBitmapFilesLocked();
+
+ for (File child : path.listFiles()) {
+ if (!child.isFile()) {
+ continue;
+ }
+ final String name = child.getName();
+ if (!usedFiles.contains(name)) {
+ if (ShortcutService.DEBUG) {
+ Slog.d(TAG, "Removing dangling bitmap file: " + child.getAbsolutePath());
+ }
+ child.delete();
+ }
+ }
+ }
+ }
+
private static String getFileName(@NonNull String path) {
final int sep = path.lastIndexOf(File.separatorChar);
if (sep == -1) {
@@ -1069,19 +1106,25 @@ class ShortcutPackage extends ShortcutPackageItem {
// Now prepare to publish manifest shortcuts.
List<ShortcutInfo> newManifestShortcutList = null;
- try {
- newManifestShortcutList = ShortcutParser.parseShortcuts(mShortcutUser.mService,
- getPackageName(), getPackageUserId(), mShareTargets);
- } catch (IOException|XmlPullParserException e) {
- Slog.e(TAG, "Failed to load shortcuts from AndroidManifest.xml.", e);
+ final int shareTargetSize;
+ synchronized (mLock) {
+ try {
+ shareTargetSize = mShareTargets.size();
+ newManifestShortcutList = ShortcutParser.parseShortcuts(mShortcutUser.mService,
+ getPackageName(), getPackageUserId(), mShareTargets);
+ } catch (IOException | XmlPullParserException e) {
+ Slog.e(TAG, "Failed to load shortcuts from AndroidManifest.xml.", e);
+ }
}
final int manifestShortcutSize = newManifestShortcutList == null ? 0
: newManifestShortcutList.size();
if (ShortcutService.DEBUG || ShortcutService.DEBUG_REBOOT) {
Slog.d(TAG,
- String.format("Package %s has %d manifest shortcut(s), and %d share target(s)",
- getPackageName(), manifestShortcutSize, mShareTargets.size()));
+ String.format(
+ "Package %s has %d manifest shortcut(s), and %d share target(s)",
+ getPackageName(), manifestShortcutSize, shareTargetSize));
}
+
if (isNewApp && (manifestShortcutSize == 0)) {
// If it's a new app, and it doesn't have manifest shortcuts, then nothing to do.
@@ -1608,6 +1651,11 @@ class ShortcutPackage extends ShortcutPackageItem {
pw.print(" (");
pw.print(Formatter.formatFileSize(mShortcutUser.mService.mContext, totalBitmapSize[0]));
pw.println(")");
+
+ pw.println();
+ synchronized (mLock) {
+ mShortcutBitmapSaver.dumpLocked(pw, " ");
+ }
}
public void dumpShortcuts(@NonNull PrintWriter pw, int matchFlags) {
@@ -1675,37 +1723,38 @@ class ShortcutPackage extends ShortcutPackageItem {
@Override
public void saveToXml(@NonNull TypedXmlSerializer out, boolean forBackup)
throws IOException, XmlPullParserException {
- final int size = mShortcuts.size();
- final int shareTargetSize = mShareTargets.size();
+ synchronized (mLock) {
+ final int size = mShortcuts.size();
+ final int shareTargetSize = mShareTargets.size();
- if (hasNoShortcut() && shareTargetSize == 0 && mApiCallCount == 0) {
- return; // nothing to write.
- }
+ if (hasNoShortcut() && shareTargetSize == 0 && mApiCallCount == 0) {
+ return; // nothing to write.
+ }
- out.startTag(null, TAG_ROOT);
+ out.startTag(null, TAG_ROOT);
- ShortcutService.writeAttr(out, ATTR_NAME, getPackageName());
- ShortcutService.writeAttr(out, ATTR_CALL_COUNT, mApiCallCount);
- ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime);
- if (!forBackup) {
- synchronized (mLock) {
- ShortcutService.writeAttr(out, ATTR_SCHEMA_VERSON, (mIsAppSearchSchemaUpToDate)
+ ShortcutService.writeAttr(out, ATTR_NAME, getPackageName());
+ ShortcutService.writeAttr(out, ATTR_CALL_COUNT, mApiCallCount);
+ ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime);
+ if (!forBackup) {
+ ShortcutService.writeAttr(out, ATTR_SCHEMA_VERSON, mIsAppSearchSchemaUpToDate
? AppSearchShortcutInfo.SCHEMA_VERSION : 0);
}
- }
- getPackageInfo().saveToXml(mShortcutUser.mService, out, forBackup);
+ getPackageInfo().saveToXml(mShortcutUser.mService, out, forBackup);
- for (int j = 0; j < size; j++) {
- saveShortcut(out, mShortcuts.valueAt(j), forBackup, getPackageInfo().isBackupAllowed());
- }
+ for (int j = 0; j < size; j++) {
+ saveShortcut(
+ out, mShortcuts.valueAt(j), forBackup, getPackageInfo().isBackupAllowed());
+ }
- if (!forBackup) {
- for (int j = 0; j < shareTargetSize; j++) {
- mShareTargets.get(j).saveToXml(out);
+ if (!forBackup) {
+ for (int j = 0; j < shareTargetSize; j++) {
+ mShareTargets.get(j).saveToXml(out);
+ }
}
- }
- out.endTag(null, TAG_ROOT);
+ out.endTag(null, TAG_ROOT);
+ }
}
private void saveShortcut(TypedXmlSerializer out, ShortcutInfo si, boolean forBackup,
@@ -1729,7 +1778,7 @@ class ShortcutPackage extends ShortcutPackageItem {
// Note: at this point no shortcuts should have bitmaps pending save, but if they do,
// just remove the bitmap.
if (si.isIconPendingSave()) {
- s.removeIconLocked(si);
+ removeIcon(si);
}
out.startTag(null, TAG_SHORTCUT);
ShortcutService.writeAttr(out, ATTR_ID, si.getId());
@@ -1891,38 +1940,38 @@ class ShortcutPackage extends ShortcutPackageItem {
synchronized (ret.mLock) {
ret.mIsAppSearchSchemaUpToDate = ShortcutService.parseIntAttribute(
parser, ATTR_SCHEMA_VERSON, 0) == AppSearchShortcutInfo.SCHEMA_VERSION;
- }
- ret.mApiCallCount = ShortcutService.parseIntAttribute(parser, ATTR_CALL_COUNT);
- ret.mLastResetTime = ShortcutService.parseLongAttribute(parser, ATTR_LAST_RESET);
+ ret.mApiCallCount = ShortcutService.parseIntAttribute(parser, ATTR_CALL_COUNT);
+ ret.mLastResetTime = ShortcutService.parseLongAttribute(parser, ATTR_LAST_RESET);
- final int outerDepth = parser.getDepth();
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type != XmlPullParser.START_TAG) {
- continue;
- }
- final int depth = parser.getDepth();
- final String tag = parser.getName();
- if (depth == outerDepth + 1) {
- switch (tag) {
- case ShortcutPackageInfo.TAG_ROOT:
- ret.getPackageInfo().loadFromXml(parser, fromBackup);
-
- continue;
- case TAG_SHORTCUT:
- final ShortcutInfo si = parseShortcut(parser, packageName,
- shortcutUser.getUserId(), fromBackup);
- // Don't use addShortcut(), we don't need to save the icon.
- ret.mShortcuts.put(si.getId(), si);
- continue;
- case TAG_SHARE_TARGET:
- ret.mShareTargets.add(ShareTargetInfo.loadFromXml(parser));
- continue;
+ final int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+ final int depth = parser.getDepth();
+ final String tag = parser.getName();
+ if (depth == outerDepth + 1) {
+ switch (tag) {
+ case ShortcutPackageInfo.TAG_ROOT:
+ ret.getPackageInfo().loadFromXml(parser, fromBackup);
+
+ continue;
+ case TAG_SHORTCUT:
+ final ShortcutInfo si = parseShortcut(parser, packageName,
+ shortcutUser.getUserId(), fromBackup);
+ // Don't use addShortcut(), we don't need to save the icon.
+ ret.mShortcuts.put(si.getId(), si);
+ continue;
+ case TAG_SHARE_TARGET:
+ ret.mShareTargets.add(ShareTargetInfo.loadFromXml(parser));
+ continue;
+ }
}
+ ShortcutService.warnForInvalidTag(depth, tag);
}
- ShortcutService.warnForInvalidTag(depth, tag);
}
return ret;
}
@@ -2126,7 +2175,9 @@ class ShortcutPackage extends ShortcutPackageItem {
@VisibleForTesting
List<ShareTargetInfo> getAllShareTargetsForTest() {
- return new ArrayList<>(mShareTargets);
+ synchronized (mLock) {
+ return new ArrayList<>(mShareTargets);
+ }
}
@Override
@@ -2265,15 +2316,19 @@ class ShortcutPackage extends ShortcutPackageItem {
private void saveShortcut(@NonNull final Collection<ShortcutInfo> shortcuts) {
Objects.requireNonNull(shortcuts);
- for (ShortcutInfo si : shortcuts) {
- mShortcuts.put(si.getId(), si);
+ synchronized (mLock) {
+ for (ShortcutInfo si : shortcuts) {
+ mShortcuts.put(si.getId(), si);
+ }
}
}
@Nullable
List<ShortcutInfo> findAll(@NonNull final Collection<String> ids) {
- return ids.stream().map(mShortcuts::get)
- .filter(Objects::nonNull).collect(Collectors.toList());
+ synchronized (mLock) {
+ return ids.stream().map(mShortcuts::get)
+ .filter(Objects::nonNull).collect(Collectors.toList());
+ }
}
private void forEachShortcut(@NonNull final Consumer<ShortcutInfo> cb) {
@@ -2292,10 +2347,12 @@ class ShortcutPackage extends ShortcutPackageItem {
private void forEachShortcutStopWhen(
@NonNull final Function<ShortcutInfo, Boolean> cb) {
- for (int i = mShortcuts.size() - 1; i >= 0; i--) {
- final ShortcutInfo si = mShortcuts.valueAt(i);
- if (cb.apply(si)) {
- return;
+ synchronized (mLock) {
+ for (int i = mShortcuts.size() - 1; i >= 0; i--) {
+ final ShortcutInfo si = mShortcuts.valueAt(i);
+ if (cb.apply(si)) {
+ return;
+ }
}
}
}
@@ -2435,6 +2492,7 @@ class ShortcutPackage extends ShortcutPackageItem {
})));
}
+ @GuardedBy("mLock")
@Override
void scheduleSaveToAppSearchLocked() {
final Map<String, ShortcutInfo> copy = new ArrayMap<>(mShortcuts);
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
index 6e0436f208e3..7800183692ca 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
@@ -16,8 +16,10 @@
package com.android.server.pm;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.pm.PackageInfo;
import android.content.pm.ShortcutInfo;
+import android.graphics.Bitmap;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.TypedXmlSerializer;
@@ -50,6 +52,9 @@ abstract class ShortcutPackageItem {
protected ShortcutUser mShortcutUser;
+ @GuardedBy("mLock")
+ protected ShortcutBitmapSaver mShortcutBitmapSaver;
+
protected final Object mLock = new Object();
protected ShortcutPackageItem(@NonNull ShortcutUser shortcutUser,
@@ -59,6 +64,7 @@ abstract class ShortcutPackageItem {
mPackageUserId = packageUserId;
mPackageName = Preconditions.checkStringNotEmpty(packageName);
mPackageInfo = Objects.requireNonNull(packageInfo);
+ mShortcutBitmapSaver = new ShortcutBitmapSaver(shortcutUser.mService);
}
/**
@@ -206,7 +212,7 @@ abstract class ShortcutPackageItem {
void saveShortcutPackageItem() {
// Wait for bitmap saves to conclude before proceeding to saving shortcuts.
- mShortcutUser.mService.waitForBitmapSaves();
+ waitForBitmapSaves();
// Save each ShortcutPackageItem in a separate Xml file.
final File path = getShortcutPackageItemFile();
if (ShortcutService.DEBUG || ShortcutService.DEBUG_REBOOT) {
@@ -221,6 +227,35 @@ abstract class ShortcutPackageItem {
}
}
+ public boolean waitForBitmapSaves() {
+ synchronized (mLock) {
+ return mShortcutBitmapSaver.waitForAllSavesLocked();
+ }
+ }
+
+ public void saveBitmap(ShortcutInfo shortcut,
+ int maxDimension, Bitmap.CompressFormat format, int quality) {
+ synchronized (mLock) {
+ mShortcutBitmapSaver.saveBitmapLocked(shortcut, maxDimension, format, quality);
+ }
+ }
+
+ /**
+ * Wait for all pending saves to finish, and then return the given shortcut's bitmap path.
+ */
+ @Nullable
+ public String getBitmapPathMayWait(ShortcutInfo shortcut) {
+ synchronized (mLock) {
+ return mShortcutBitmapSaver.getBitmapPathMayWaitLocked(shortcut);
+ }
+ }
+
+ public void removeIcon(ShortcutInfo shortcut) {
+ synchronized (mLock) {
+ mShortcutBitmapSaver.removeIcon(shortcut);
+ }
+ }
+
void removeShortcutPackageItem() {
synchronized (mLock) {
getShortcutPackageItemFile().delete();
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 780f976d2a40..f2bcf5e461a7 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -363,7 +363,6 @@ public class ShortcutService extends IShortcutService.Stub {
private final RoleManager mRoleManager;
private final ShortcutRequestPinProcessor mShortcutRequestPinProcessor;
- private final ShortcutBitmapSaver mShortcutBitmapSaver;
private final ShortcutDumpFiles mShortcutDumpFiles;
@GuardedBy("mLock")
@@ -490,7 +489,6 @@ public class ShortcutService extends IShortcutService.Stub {
mRoleManager = Objects.requireNonNull(mContext.getSystemService(RoleManager.class));
mShortcutRequestPinProcessor = new ShortcutRequestPinProcessor(this, mLock);
- mShortcutBitmapSaver = new ShortcutBitmapSaver(this);
mShortcutDumpFiles = new ShortcutDumpFiles(this);
mIsAppSearchEnabled = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.SHORTCUT_APPSEARCH_INTEGRATION, true)
@@ -1063,8 +1061,6 @@ public class ShortcutService extends IShortcutService.Stub {
Slog.d(TAG, "Saving to " + path);
}
- mShortcutBitmapSaver.waitForAllSavesLocked();
-
path.getParentFile().mkdirs();
final AtomicFile file = new AtomicFile(path);
FileOutputStream os = null;
@@ -1388,15 +1384,12 @@ public class ShortcutService extends IShortcutService.Stub {
// === Caller validation ===
- void removeIconLocked(ShortcutInfo shortcut) {
- mShortcutBitmapSaver.removeIcon(shortcut);
- }
-
public void cleanupBitmapsForPackage(@UserIdInt int userId, String packageName) {
final File packagePath = new File(getUserBitmapFilePath(userId), packageName);
if (!packagePath.isDirectory()) {
return;
}
+ // ShortcutPackage is already removed at this point, we can safely remove the folder.
if (!(FileUtils.deleteContents(packagePath) && packagePath.delete())) {
Slog.w(TAG, "Unable to remove directory " + packagePath);
}
@@ -1437,38 +1430,12 @@ public class ShortcutService extends IShortcutService.Stub {
}
cleanupBitmapsForPackage(userId, packageName);
} else {
- cleanupDanglingBitmapFilesLocked(userId, user, packageName, child);
+ user.getPackageShortcuts(packageName).cleanupDanglingBitmapFiles(child);
}
}
logDurationStat(Stats.CLEANUP_DANGLING_BITMAPS, start);
}
- /**
- * Remove dangling bitmap files for a package.
- *
- * Note this method must be called with the lock held after calling
- * {@link ShortcutBitmapSaver#waitForAllSavesLocked()} to make sure there's no pending bitmap
- * saves are going on.
- */
- private void cleanupDanglingBitmapFilesLocked(@UserIdInt int userId, @NonNull ShortcutUser user,
- @NonNull String packageName, @NonNull File path) {
- final ArraySet<String> usedFiles =
- user.getPackageShortcuts(packageName).getUsedBitmapFiles();
-
- for (File child : path.listFiles()) {
- if (!child.isFile()) {
- continue;
- }
- final String name = child.getName();
- if (!usedFiles.contains(name)) {
- if (DEBUG) {
- Slog.d(TAG, "Removing dangling bitmap file: " + child.getAbsolutePath());
- }
- child.delete();
- }
- }
- }
-
@VisibleForTesting
static class FileOutputStreamWithPath extends FileOutputStream {
private final File mFile;
@@ -1513,7 +1480,7 @@ public class ShortcutService extends IShortcutService.Stub {
}
}
- void saveIconAndFixUpShortcutLocked(ShortcutInfo shortcut) {
+ void saveIconAndFixUpShortcutLocked(ShortcutPackage p, ShortcutInfo shortcut) {
if (shortcut.hasIconFile() || shortcut.hasIconResource() || shortcut.hasIconUri()) {
return;
}
@@ -1521,7 +1488,7 @@ public class ShortcutService extends IShortcutService.Stub {
final long token = injectClearCallingIdentity();
try {
// Clear icon info on the shortcut.
- removeIconLocked(shortcut);
+ p.removeIcon(shortcut);
final Icon icon = shortcut.getIcon();
if (icon == null) {
@@ -1560,8 +1527,7 @@ public class ShortcutService extends IShortcutService.Stub {
// just in case.
throw ShortcutInfo.getInvalidIconException();
}
- mShortcutBitmapSaver.saveBitmapLocked(shortcut,
- maxIconDimension, mIconPersistFormat, mIconPersistQuality);
+ p.saveBitmap(shortcut, maxIconDimension, mIconPersistFormat, mIconPersistQuality);
} finally {
// Once saved, we won't use the original icon information, so null it out.
shortcut.clearIcon();
@@ -2110,7 +2076,7 @@ public class ShortcutService extends IShortcutService.Stub {
final boolean replacingIcon = (source.getIcon() != null);
if (replacingIcon) {
- removeIconLocked(target);
+ ps.removeIcon(target);
}
// Note copyNonNullFieldsFrom() does the "updatable with?" check too.
@@ -2118,7 +2084,7 @@ public class ShortcutService extends IShortcutService.Stub {
target.setTimestamp(injectCurrentTimeMillis());
if (replacingIcon) {
- saveIconAndFixUpShortcutLocked(target);
+ saveIconAndFixUpShortcutLocked(ps, target);
}
// When we're updating any resource related fields, re-extract the res
@@ -3463,7 +3429,7 @@ public class ShortcutService extends IShortcutService.Stub {
if (shortcutInfo == null) {
return null;
}
- return getShortcutIconParcelFileDescriptor(shortcutInfo);
+ return getShortcutIconParcelFileDescriptor(p, shortcutInfo);
}
}
@@ -3476,6 +3442,7 @@ public class ShortcutService extends IShortcutService.Stub {
Objects.requireNonNull(shortcutId, "shortcutId");
// Checks shortcuts in memory first
+ final ShortcutPackage p;
synchronized (mLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -3483,8 +3450,7 @@ public class ShortcutService extends IShortcutService.Stub {
getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
.attemptToRestoreIfNeededAndSave();
- final ShortcutPackage p = getUserShortcutsLocked(userId)
- .getPackageShortcutsIfExists(packageName);
+ p = getUserShortcutsLocked(userId).getPackageShortcutsIfExists(packageName);
if (p == null) {
cb.complete(null);
return;
@@ -3492,24 +3458,23 @@ public class ShortcutService extends IShortcutService.Stub {
final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId);
if (shortcutInfo != null) {
- cb.complete(getShortcutIconParcelFileDescriptor(shortcutInfo));
+ cb.complete(getShortcutIconParcelFileDescriptor(p, shortcutInfo));
return;
}
}
// Otherwise check persisted shortcuts
- getShortcutInfoAsync(launcherUserId, packageName, shortcutId, userId, si -> {
- cb.complete(getShortcutIconParcelFileDescriptor(si));
- });
+ getShortcutInfoAsync(launcherUserId, packageName, shortcutId, userId, si ->
+ cb.complete(getShortcutIconParcelFileDescriptor(p, si)));
}
@Nullable
private ParcelFileDescriptor getShortcutIconParcelFileDescriptor(
- @Nullable final ShortcutInfo shortcutInfo) {
- if (shortcutInfo == null || !shortcutInfo.hasIconFile()) {
+ @Nullable final ShortcutPackage p, @Nullable final ShortcutInfo shortcutInfo) {
+ if (p == null || shortcutInfo == null || !shortcutInfo.hasIconFile()) {
return null;
}
- final String path = mShortcutBitmapSaver.getBitmapPathMayWaitLocked(shortcutInfo);
+ final String path = p.getBitmapPathMayWait(shortcutInfo);
if (path == null) {
Slog.w(TAG, "null bitmap detected in getShortcutIconFd()");
return null;
@@ -4772,9 +4737,6 @@ public class ShortcutService extends IShortcutService.Stub {
}
pw.println();
- mShortcutBitmapSaver.dumpLocked(pw, " ");
-
- pw.println();
}
for (int i = 0; i < mUsers.size(); i++) {
@@ -5347,9 +5309,11 @@ public class ShortcutService extends IShortcutService.Stub {
}
}
- void waitForBitmapSaves() {
+ @VisibleForTesting
+ void waitForBitmapSavesForTest() {
synchronized (mLock) {
- mShortcutBitmapSaver.waitForAllSavesLocked();
+ forEachLoadedUserLocked(u ->
+ u.forAllPackageItems(ShortcutPackageItem::waitForBitmapSaves));
}
}
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index 75e18b547c55..b9fd2fdb9630 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -401,6 +401,7 @@ class ShortcutUser {
private void saveShortcutPackageItem(TypedXmlSerializer out, ShortcutPackageItem spi,
boolean forBackup) throws IOException, XmlPullParserException {
+ spi.waitForBitmapSaves();
if (forBackup) {
if (spi.getPackageUserId() != spi.getOwnerUserId()) {
return; // Don't save cross-user information.
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 0dabff8370ba..bcdf4291ed41 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1430,6 +1430,8 @@ public class UserManagerService extends IUserManager.Stub {
/**
* Returns a UserInfo object with the name filled in, for Owner and Guest, or the original
* if the name is already set.
+ *
+ * Note: Currently, the resulting name can be null if a user was truly created with a null name.
*/
private UserInfo userWithName(UserInfo orig) {
if (orig != null && orig.name == null) {
@@ -1638,7 +1640,7 @@ public class UserManagerService extends IUserManager.Stub {
}
@Override
- public String getUserName() {
+ public @NonNull String getUserName() {
final int callingUid = Binder.getCallingUid();
if (!hasQueryOrCreateUsersPermission()
&& !hasPermissionGranted(
@@ -1649,7 +1651,10 @@ public class UserManagerService extends IUserManager.Stub {
final int userId = UserHandle.getUserId(callingUid);
synchronized (mUsersLock) {
UserInfo userInfo = userWithName(getUserInfoLU(userId));
- return userInfo == null ? "" : userInfo.name;
+ if (userInfo != null && userInfo.name != null) {
+ return userInfo.name;
+ }
+ return "";
}
}
@@ -4165,7 +4170,7 @@ public class UserManagerService extends IUserManager.Stub {
* @return the converted user, or {@code null} if no pre-created user could be converted.
*/
private @Nullable UserInfo convertPreCreatedUserIfPossible(String userType,
- @UserInfoFlag int flags, String name, @Nullable Object token) {
+ @UserInfoFlag int flags, @Nullable String name, @Nullable Object token) {
final UserData preCreatedUserData;
synchronized (mUsersLock) {
preCreatedUserData = getPreCreatedUserLU(userType);
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 2277d8a8ecee..7be83b03243a 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -2816,14 +2816,12 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt
}
// Remove review flag as it is not necessary anymore
- // TODO(b/227186603) re-enable check for notification permission once
- // droidfood state has been cleared
- //if (!NOTIFICATION_PERMISSIONS.contains(perm)) {
+ if (!NOTIFICATION_PERMISSIONS.contains(perm)) {
if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
flags &= ~FLAG_PERMISSION_REVIEW_REQUIRED;
wasChanged = true;
}
- //}
+ }
if ((flags & FLAG_PERMISSION_REVOKED_COMPAT) != 0
&& !isPermissionSplitFromNonRuntime(permName,
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index b14214189833..d8e7fbe8b296 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -27,7 +27,6 @@ import static android.content.pm.PackageManager.FEATURE_LEANBACK;
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
import static android.content.pm.PackageManager.FEATURE_WATCH;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.os.BatteryManager.BATTERY_PLUGGED_WIRELESS;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.O;
import static android.provider.Settings.Secure.VOLUME_HUSH_OFF;
@@ -130,7 +129,6 @@ import android.media.AudioManagerInternal;
import android.media.AudioSystem;
import android.media.IAudioService;
import android.media.session.MediaSessionLegacyHelper;
-import android.os.BatteryManagerInternal;
import android.os.Binder;
import android.os.Bundle;
import android.os.DeviceIdleManager;
@@ -396,7 +394,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
PowerManagerInternal mPowerManagerInternal;
IStatusBarService mStatusBarService;
StatusBarManagerInternal mStatusBarManagerInternal;
- BatteryManagerInternal mBatteryManagerInternal;
AudioManagerInternal mAudioManagerInternal;
DisplayManager mDisplayManager;
DisplayManagerInternal mDisplayManagerInternal;
@@ -791,8 +788,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
@Override
public void onWakeUp() {
synchronized (mLock) {
- if (shouldEnableWakeGestureLp()
- && getBatteryManagerInternal().getPlugType() != BATTERY_PLUGGED_WIRELESS) {
+ if (shouldEnableWakeGestureLp()) {
performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, false,
"Wake Up");
wakeUp(SystemClock.uptimeMillis(), mAllowTheaterModeWakeFromWakeGesture,
@@ -849,15 +845,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
}
- BatteryManagerInternal getBatteryManagerInternal() {
- synchronized (mServiceAcquireLock) {
- if (mBatteryManagerInternal == null) {
- mBatteryManagerInternal =
- LocalServices.getService(BatteryManagerInternal.class);
- }
- return mBatteryManagerInternal;
- }
- }
// returns true if the key was handled and should not be passed to the user
private boolean backKeyPress() {
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
index 08344170b02d..3c779f387673 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
@@ -727,7 +727,8 @@ public final class SensorPrivacyService extends SystemService {
return false;
}
- if (mKeyguardManager != null && mKeyguardManager.isDeviceLocked(userId)) {
+ if (requiresAuthentication() && mKeyguardManager != null
+ && mKeyguardManager.isDeviceLocked(userId)) {
Log.i(TAG, "Can't change mic/cam toggle while device is locked");
return false;
}
@@ -993,6 +994,13 @@ public final class SensorPrivacyService extends SystemService {
}
@Override
+ public boolean requiresAuthentication() {
+ enforceObserveSensorPrivacyPermission();
+ return mContext.getResources()
+ .getBoolean(R.bool.config_sensorPrivacyRequiresAuthentication);
+ }
+
+ @Override
public void showSensorUseDialog(int sensor) {
if (Binder.getCallingUid() != Process.SYSTEM_UID) {
throw new SecurityException("Can only be called by the system uid");
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index b6855726c122..d48f26332017 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -1650,13 +1650,11 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
}
@Override
- public void onBubbleNotificationSuppressionChanged(String key, boolean isNotifSuppressed,
- boolean isBubbleSuppressed) {
+ public void onBubbleMetadataFlagChanged(String key, int flags) {
enforceStatusBarService();
final long identity = Binder.clearCallingIdentity();
try {
- mNotificationDelegate.onBubbleNotificationSuppressionChanged(key, isNotifSuppressed,
- isBubbleSuppressed);
+ mNotificationDelegate.onBubbleMetadataFlagChanged(key, flags);
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
index d3748140a5a5..4b8c7c176fda 100644
--- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java
+++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
@@ -122,16 +122,9 @@ public class TrustAgentWrapper {
if (!TrustManagerService.ENABLE_ACTIVE_UNLOCK_FLAG) {
return;
}
- if (!mWaitingForTrustableDowngrade) {
- return;
- }
// are these the broadcasts we want to listen to
- if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())
- || Intent.ACTION_USER_PRESENT.equals(intent.getAction())) {
- mTrusted = false;
- mTrustable = true;
- mWaitingForTrustableDowngrade = false;
- mTrustManagerService.updateTrust(mUserId, 0);
+ if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
+ downgradeToTrustable();
}
}
};
@@ -480,8 +473,7 @@ public class TrustAgentWrapper {
final String pathUri = mAlarmIntent.toUri(Intent.URI_INTENT_SCHEME);
alarmFilter.addDataPath(pathUri, PatternMatcher.PATTERN_LITERAL);
- IntentFilter trustableFilter = new IntentFilter(Intent.ACTION_USER_PRESENT);
- trustableFilter.addAction(Intent.ACTION_SCREEN_OFF);
+ IntentFilter trustableFilter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
// Schedules a restart for when connecting times out. If the connection succeeds,
// the restart is canceled in mCallback's onConnected.
@@ -668,6 +660,19 @@ public class TrustAgentWrapper {
mTrustable = false;
}
+ /**
+ * Downgrades the trustagent to trustable as a result of a keyguard or screen related event, and
+ * then updates the trust state of the phone to reflect the change.
+ */
+ public void downgradeToTrustable() {
+ if (mWaitingForTrustableDowngrade) {
+ mWaitingForTrustableDowngrade = false;
+ mTrusted = false;
+ mTrustable = true;
+ mTrustManagerService.updateTrust(mUserId, 0);
+ }
+ }
+
public boolean isManagingTrust() {
return mManagingTrust && !mTrustDisabledByDpm;
}
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 8f4ddea1c30c..80ce70de2138 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -1184,6 +1184,22 @@ public class TrustManagerService extends SystemService {
return false;
}
+ /**
+ * We downgrade to trustable whenever keyguard changes its showing value.
+ * - becomes showing: something has caused the device to show keyguard which happens due to
+ * user intent to lock the device either through direct action or a timeout
+ * - becomes not showing: keyguard was dismissed and we no longer need to keep the device
+ * unlocked
+ * */
+ private void dispatchTrustableDowngrade() {
+ for (int i = 0; i < mActiveAgents.size(); i++) {
+ AgentInfo info = mActiveAgents.valueAt(i);
+ if (info.userId == mCurrentUser) {
+ info.agent.downgradeToTrustable();
+ }
+ }
+ }
+
private List<String> getTrustGrantedMessages(int userId) {
if (!mStrongAuthTracker.isTrustAllowedForUser(userId)) {
return new ArrayList<>();
@@ -1752,6 +1768,7 @@ public class TrustManagerService extends SystemService {
refreshDeviceLockedForUser(UserHandle.USER_ALL);
break;
case MSG_KEYGUARD_SHOWING_CHANGED:
+ dispatchTrustableDowngrade();
refreshDeviceLockedForUser(mCurrentUser);
break;
case MSG_START_USER:
diff --git a/services/core/java/com/android/server/utils/WatchedArrayList.java b/services/core/java/com/android/server/utils/WatchedArrayList.java
index 6059f9675e34..75e39ebb32d8 100644
--- a/services/core/java/com/android/server/utils/WatchedArrayList.java
+++ b/services/core/java/com/android/server/utils/WatchedArrayList.java
@@ -416,7 +416,7 @@ public class WatchedArrayList<E> extends WatchableImpl
dst.mStorage.ensureCapacity(end);
for (int i = 0; i < end; i++) {
final E val = Snapshots.maybeSnapshot(src.get(i));
- dst.add(i, val);
+ dst.add(val);
}
dst.seal();
}
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 63619e543a6d..f1dbad61a76d 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -446,6 +446,43 @@ final class AccessibilityController {
// Not relevant for the window observer.
}
+ public Pair<Matrix, MagnificationSpec> getWindowTransformationMatrixAndMagnificationSpec(
+ IBinder token) {
+ synchronized (mService.mGlobalLock) {
+ final Matrix transformationMatrix = new Matrix();
+ final MagnificationSpec magnificationSpec = new MagnificationSpec();
+
+ final WindowState windowState = mService.mWindowMap.get(token);
+ if (windowState != null) {
+ windowState.getTransformationMatrix(new float[9], transformationMatrix);
+
+ if (hasCallbacks()) {
+ final MagnificationSpec otherMagnificationSpec =
+ getMagnificationSpecForWindow(windowState);
+ if (otherMagnificationSpec != null && !otherMagnificationSpec.isNop()) {
+ magnificationSpec.setTo(otherMagnificationSpec);
+ }
+ }
+ }
+
+ return new Pair<>(transformationMatrix, magnificationSpec);
+ }
+ }
+
+ MagnificationSpec getMagnificationSpecForWindow(WindowState windowState) {
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+ mAccessibilityTracing.logTrace(TAG + ".getMagnificationSpecForWindow",
+ FLAGS_MAGNIFICATION_CALLBACK,
+ "windowState={" + windowState + "}");
+ }
+ final int displayId = windowState.getDisplayId();
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+ if (displayMagnifier != null) {
+ return displayMagnifier.getMagnificationSpecForWindow(windowState);
+ }
+ return null;
+ }
+
boolean hasCallbacks() {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK
| FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index b97ee7ef76b3..b2f27ebaf513 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2732,8 +2732,8 @@ class ActivityStarter {
intentActivity.getTaskFragment().clearLastPausedActivity();
Task intentTask = intentActivity.getTask();
- // Only update the target-root-task when it is not indicated.
if (mTargetRootTask == null) {
+ // Update launch target task when it is not indicated.
if (mSourceRecord != null && mSourceRecord.mLaunchRootTask != null) {
// Inherit the target-root-task from source to ensure trampoline activities will be
// launched into the same root task.
@@ -2742,6 +2742,17 @@ class ActivityStarter {
mTargetRootTask = getOrCreateRootTask(mStartActivity, mLaunchFlags, intentTask,
mOptions);
}
+ } else {
+ // If a launch target indicated, and the matching task is already in the adjacent task
+ // of the launch target. Adjust to use the adjacent task as its launch target. So the
+ // existing task will be launched into the closer one and won't be reparent redundantly.
+ // TODO(b/231541706): Migrate the logic to wm-shell after having proper APIs to help
+ // resolve target task without actually starting the activity.
+ final Task adjacentTargetTask = mTargetRootTask.getAdjacentTaskFragment() != null
+ ? mTargetRootTask.getAdjacentTaskFragment().asTask() : null;
+ if (adjacentTargetTask != null && intentActivity.isDescendantOf(adjacentTargetTask)) {
+ mTargetRootTask = adjacentTargetTask;
+ }
}
// If the target task is not in the front, then we need to bring it to the front...
@@ -2771,7 +2782,7 @@ class ActivityStarter {
intentActivity.setTaskToAffiliateWith(mSourceRecord.getTask());
}
- if (mTargetRootTask == intentActivity.getRootTask()) {
+ if (intentActivity.isDescendantOf(mTargetRootTask)) {
// TODO(b/151572268): Figure out a better way to move tasks in above 2-levels
// tasks hierarchies.
if (mTargetRootTask != intentTask
@@ -2818,19 +2829,6 @@ class ActivityStarter {
mTargetRootTask = intentActivity.getRootTask();
mSupervisor.handleNonResizableTaskIfNeeded(intentTask, WINDOWING_MODE_UNDEFINED,
mRootWindowContainer.getDefaultTaskDisplayArea(), mTargetRootTask);
-
- // We need to check if there is a launch root task in TDA for this target root task.
- // If it exist, we need to reparent target root task from TDA to launch root task.
- final TaskDisplayArea tda = mTargetRootTask.getDisplayArea();
- final Task launchRootTask = tda.getLaunchRootTask(mTargetRootTask.getWindowingMode(),
- mTargetRootTask.getActivityType(), null /** options */, mSourceRootTask,
- mLaunchFlags);
- // If target root task is created by organizer, let organizer handle reparent itself.
- if (!mTargetRootTask.mCreatedByOrganizer && launchRootTask != null
- && launchRootTask != mTargetRootTask) {
- mTargetRootTask.reparent(launchRootTask, POSITION_TOP);
- mTargetRootTask = launchRootTask;
- }
}
private void resumeTargetRootTaskIfNeeded() {
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index ee7d9a9b3f2d..46ce43335f01 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -153,10 +153,10 @@ class BLASTSyncEngine {
for (WindowContainer wc : mRootMembers) {
wc.waitForSyncTransactionCommit(wcAwaitingCommit);
}
- final Runnable callback = new Runnable() {
+ class CommitCallback implements Runnable {
// Can run a second time if the action completes after the timeout.
boolean ran = false;
- public void run() {
+ public void onCommitted() {
synchronized (mWm.mGlobalLock) {
if (ran) {
return;
@@ -171,8 +171,23 @@ class BLASTSyncEngine {
wcAwaitingCommit.clear();
}
}
+
+ // Called in timeout
+ @Override
+ public void run() {
+ // Sometimes we get a trace, sometimes we get a bugreport without
+ // a trace. Since these kind of ANRs can trigger such an issue,
+ // try and ensure we will have some visibility in both cases.
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "onTransactionCommitTimeout");
+ Slog.e(TAG, "WM sent Transaction to organized, but never received" +
+ " commit callback. Application ANR likely to follow.");
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ onCommitted();
+
+ }
};
- merged.addTransactionCommittedListener((r) -> { r.run(); }, callback::run);
+ CommitCallback callback = new CommitCallback();
+ merged.addTransactionCommittedListener((r) -> { r.run(); }, callback::onCommitted);
mWm.mH.postDelayed(callback, BLAST_TIMEOUT_DURATION);
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "onTransactionReady");
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index b37f980ce9a0..247117e707c8 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -124,8 +124,7 @@ class BackNavigationController {
LocalServices.getService(WindowManagerInternal.class);
IBinder focusedWindowToken = windowManagerInternal.getFocusedWindowToken();
- window = wmService.windowForClientLocked(null, focusedWindowToken,
- false /* throwOnError */);
+ window = wmService.getFocusedWindowLocked();
if (window == null) {
EmbeddedWindowController.EmbeddedWindow embeddedWindow =
@@ -147,12 +146,6 @@ class BackNavigationController {
}
if (window == null) {
- window = wmService.getFocusedWindowLocked();
- ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
- "Focused window found using wmService.getFocusedWindowLocked()");
- }
-
- if (window == null) {
// We don't have any focused window, fallback ont the top currentTask of the focused
// display.
ProtoLog.w(WM_DEBUG_BACK_PREVIEW,
@@ -194,7 +187,6 @@ class BackNavigationController {
if (backType == BackNavigationInfo.TYPE_CALLBACK
|| currentActivity == null
|| currentTask == null
- || currentTask.getDisplayContent().getImeContainer().isVisible()
|| currentActivity.isActivityTypeHome()) {
return infoBuilder
.setType(backType)
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 2975a95426bb..d0baa238f732 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -2922,7 +2922,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
DisplayCutout loadDisplayCutout(int displayWidth, int displayHeight) {
- if (mDisplayPolicy == null) {
+ if (mDisplayPolicy == null || mInitialDisplayCutout == null) {
return null;
}
return DisplayCutout.fromResourcesRectApproximation(
@@ -2931,7 +2931,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
RoundedCorners loadRoundedCorners(int displayWidth, int displayHeight) {
- if (mDisplayPolicy == null) {
+ if (mDisplayPolicy == null || mInitialRoundedCorners == null) {
return null;
}
return RoundedCorners.fromResources(
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 67dd89ee295c..33cdd2e98113 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -270,6 +270,22 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal
}
}
+ @Override
+ public void notifyPointerDisplayIdChanged(int displayId, float x, float y) {
+ synchronized (mService.mGlobalLock) {
+ mService.setMousePointerDisplayId(displayId);
+ if (displayId == Display.INVALID_DISPLAY) return;
+
+ final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
+ if (dc == null) {
+ Slog.wtf(TAG, "The mouse pointer was moved to display " + displayId
+ + " that does not have a valid DisplayContent.");
+ return;
+ }
+ mService.restorePointerIconLocked(dc, x, y);
+ }
+ }
+
/** Waits until the built-in input devices have been configured. */
public boolean waitForInputDevicesReady(long timeoutMillis) {
synchronized (mInputDevicesReadyMonitor) {
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 65062e576d55..ea72e12783c3 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -573,7 +573,7 @@ final class InputMonitor {
recentsAnimationController.getTargetAppDisplayArea();
if (targetDA != null) {
mRecentsAnimationInputConsumer.reparent(mInputTransaction, targetDA);
- mRecentsAnimationInputConsumer.show(mInputTransaction, MAX_VALUE - 1);
+ mRecentsAnimationInputConsumer.show(mInputTransaction, MAX_VALUE - 2);
mAddRecentsAnimationInputConsumerHandle = false;
}
}
@@ -584,10 +584,14 @@ final class InputMonitor {
final Task rootTask = w.getTask().getRootTask();
mPipInputConsumer.mWindowHandle.replaceTouchableRegionWithCrop(
rootTask.getSurfaceControl());
+ final DisplayArea targetDA = rootTask.getDisplayArea();
// We set the layer to z=MAX-1 so that it's always on top.
- mPipInputConsumer.reparent(mInputTransaction, rootTask);
- mPipInputConsumer.show(mInputTransaction, MAX_VALUE - 1);
- mAddPipInputConsumerHandle = false;
+ if (targetDA != null) {
+ mPipInputConsumer.layout(mInputTransaction, rootTask.getBounds());
+ mPipInputConsumer.reparent(mInputTransaction, targetDA);
+ mPipInputConsumer.show(mInputTransaction, MAX_VALUE - 1);
+ mAddPipInputConsumerHandle = false;
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index efe617d86d4e..2bae59a93048 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -19,7 +19,6 @@ package com.android.server.wm;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
@@ -602,22 +601,12 @@ public class RecentsAnimationController implements DeathRecipient {
|| mDisplayContent.getAsyncRotationController() != null) {
return;
}
- boolean shouldTranslateNavBar = false;
- final boolean isDisplayLandscape =
- mDisplayContent.getConfiguration().orientation == ORIENTATION_LANDSCAPE;
for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
final TaskAnimationAdapter adapter = mPendingAnimations.get(i);
final Task task = adapter.mTask;
- final TaskFragment adjacentTask = task.getRootTask().getAdjacentTaskFragment();
- final boolean inSplitScreen = task.inSplitScreen();
- if (task.isActivityTypeHomeOrRecents()
- // Skip if the task is in split screen and in landscape.
- || (inSplitScreen && isDisplayLandscape)
- // Skip if the task is the top task in split screen.
- || (inSplitScreen && task.getBounds().top < adjacentTask.getBounds().top)) {
+ if (task.isActivityTypeHomeOrRecents()) {
continue;
}
- shouldTranslateNavBar = inSplitScreen;
mNavBarAttachedApp = task.getTopVisibleActivity();
break;
}
@@ -630,9 +619,7 @@ public class RecentsAnimationController implements DeathRecipient {
navWindow.mToken.cancelAnimation();
final SurfaceControl.Transaction t = navWindow.mToken.getPendingTransaction();
final SurfaceControl navSurfaceControl = navWindow.mToken.getSurfaceControl();
- if (shouldTranslateNavBar) {
- navWindow.setSurfaceTranslationY(-mNavBarAttachedApp.getBounds().top);
- }
+ navWindow.setSurfaceTranslationY(-mNavBarAttachedApp.getBounds().top);
t.reparent(navSurfaceControl, mNavBarAttachedApp.getSurfaceControl());
t.show(navSurfaceControl);
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
index 4068a97a881a..c7f8a1e2068a 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
@@ -158,7 +158,7 @@ class SurfaceAnimationRunner {
void continueStartingAnimations() {
synchronized (mLock) {
mAnimationStartDeferred = false;
- if (!mPendingAnimations.isEmpty()) {
+ if (!mPendingAnimations.isEmpty() && mPreProcessingAnimations.isEmpty()) {
mChoreographer.postFrameCallback(this::startAnimations);
}
}
@@ -204,7 +204,7 @@ class SurfaceAnimationRunner {
mPreProcessingAnimations.remove(animationLeash);
mPendingAnimations.put(animationLeash, runningAnim);
- if (!mAnimationStartDeferred) {
+ if (!mAnimationStartDeferred && mPreProcessingAnimations.isEmpty()) {
mChoreographer.postFrameCallback(this::startAnimations);
}
}
@@ -214,7 +214,7 @@ class SurfaceAnimationRunner {
if (!requiresEdgeExtension) {
mPendingAnimations.put(animationLeash, runningAnim);
- if (!mAnimationStartDeferred) {
+ if (!mAnimationStartDeferred && mPreProcessingAnimations.isEmpty()) {
mChoreographer.postFrameCallback(this::startAnimations);
}
@@ -330,6 +330,14 @@ class SurfaceAnimationRunner {
private void startAnimations(long frameTimeNanos) {
synchronized (mLock) {
+ if (!mPreProcessingAnimations.isEmpty()) {
+ // We only want to start running animations once all mPreProcessingAnimations have
+ // been processed to ensure preprocessed animations start in sync.
+ // NOTE: This means we might delay running animations that require preprocessing if
+ // new animations that also require preprocessing are requested before the previous
+ // ones have finished (see b/227449117).
+ return;
+ }
startPendingAnimationsLocked();
}
mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, 0);
@@ -553,4 +561,4 @@ class SurfaceAnimationRunner {
return mAnimationHandler;
}
}
-}
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 718ce2870f10..60c280cb61f9 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -28,7 +28,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.activityTypeToString;
@@ -1720,13 +1719,6 @@ class Task extends TaskFragment {
&& (topTask == null || topTask.supportsSplitScreenWindowingModeInner(tda));
}
- /** Returns {@code true} if this task is currently in split-screen. */
- boolean inSplitScreen() {
- return getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW
- && getCreatedByOrganizerTask() != null
- && getCreatedByOrganizerTask().getAdjacentTaskFragment() != null;
- }
-
private boolean supportsSplitScreenWindowingModeInner(@Nullable TaskDisplayArea tda) {
return super.supportsSplitScreenWindowingMode()
&& mAtmService.mSupportsSplitScreenMultiWindow
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 1176182ede50..ce406e4ecb20 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -1163,20 +1163,21 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
}
}
- // For a better split UX, If a task is launching from a created-by-organizer task, it should
- // be launched into the same created-by-organizer task as well. Unless, the candidate task
- // is already positioned in the split.
- Task preferredRootInSplit = sourceTask != null && sourceTask.inSplitScreen()
- ? sourceTask.getCreatedByOrganizerTask() : null;
- if (preferredRootInSplit != null) {
- if (candidateTask != null) {
- final Task candidateRoot = candidateTask.getCreatedByOrganizerTask();
- if (candidateRoot != null && candidateRoot != preferredRootInSplit
- && preferredRootInSplit == candidateRoot.getAdjacentTaskFragment()) {
- preferredRootInSplit = candidateRoot;
+ // If a task is launching from a created-by-organizer task, it should be launched into the
+ // same created-by-organizer task as well. Unless, the candidate task is already positioned
+ // in the another adjacent task.
+ if (sourceTask != null) {
+ Task launchTarget = sourceTask.getCreatedByOrganizerTask();
+ if (launchTarget != null && launchTarget.getAdjacentTaskFragment() != null) {
+ if (candidateTask != null) {
+ final Task candidateRoot = candidateTask.getCreatedByOrganizerTask();
+ if (candidateRoot != null && candidateRoot != launchTarget
+ && launchTarget == candidateRoot.getAdjacentTaskFragment()) {
+ launchTarget = candidateRoot;
+ }
}
+ return launchTarget;
}
- return preferredRootInSplit;
}
return null;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index c7bc51356c76..2f00bc821678 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -3741,8 +3741,16 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
// Otherwise this is the "root" of a synced subtree, so continue on to preparation.
}
+
// This container's situation has changed so we need to restart its sync.
- mSyncState = SYNC_STATE_NONE;
+ // We cannot reset the sync without a chance of a deadlock since it will request a new
+ // buffer from the app process. This could cause issues if the app has run out of buffers
+ // since the previous buffer was already synced and is still held in a transaction.
+ // Resetting syncState violates the policies outlined in BlastSyncEngine.md so for now
+ // disable this when shell transitions is disabled.
+ if (mTransitionController.isShellTransitionsEnabled()) {
+ mSyncState = SYNC_STATE_NONE;
+ }
prepareSync();
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index f8bc26a7d91d..c0d7d1362ac3 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -23,17 +23,18 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ClipData;
import android.content.Context;
+import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.display.DisplayManagerInternal;
import android.os.Bundle;
import android.os.IBinder;
+import android.util.Pair;
import android.view.Display;
import android.view.IInputFilter;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IWindow;
import android.view.InputChannel;
-import android.view.InputWindowHandle;
import android.view.MagnificationSpec;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
@@ -459,6 +460,17 @@ public abstract class WindowManagerInternal {
public abstract void getWindowFrame(IBinder token, Rect outBounds);
/**
+ * Get the transformation matrix and MagnificationSpec given its token.
+ *
+ * @param token The token.
+ * @return The pair of the transformation matrix and magnification spec.
+ */
+ // TODO (b/231663133): Long term solution for tracking window when the
+ // FLAG_RETRIEVE_INTERACTIVE_WINDOWS is unset.
+ public abstract Pair<Matrix, MagnificationSpec>
+ getWindowTransformationMatrixAndMagnificationSpec(IBinder token);
+
+ /**
* Opens the global actions dialog.
*/
public abstract void showGlobalActions();
@@ -849,24 +861,12 @@ public abstract class WindowManagerInternal {
public abstract SurfaceControl getHandwritingSurfaceForDisplay(int displayId);
/**
- * Replaces the touchable region of the provided input surface with the crop of the window with
- * the provided token. This method will associate the inputSurface with a copy of
- * the given inputWindowHandle, where the copy is configured using
- * {@link InputWindowHandle#replaceTouchableRegionWithCrop(SurfaceControl)} with the surface
- * of the provided windowToken.
- *
- * This is a no-op if windowToken is not valid or the window is not found.
- *
- * This does not change any other properties of the inputSurface.
- *
- * This method exists to avoid leaking the window's SurfaceControl outside WindowManagerService.
+ * Returns {@code true} if the given point is within the window bounds of the given window.
*
- * @param inputSurface The surface for which the touchable region should be set.
- * @param inputWindowHandle The {@link InputWindowHandle} for the input surface.
- * @param windowToken The window whose bounds should be used as the touchable region for the
- * inputSurface.
+ * @param windowToken the window whose bounds should be used for the hit test.
+ * @param displayX the x coordinate of the test point in the display's coordinate space.
+ * @param displayY the y coordinate of the test point in the display's coordinate space.
*/
- public abstract void replaceInputSurfaceTouchableRegionWithWindowCrop(
- @NonNull SurfaceControl inputSurface, @NonNull InputWindowHandle inputWindowHandle,
- @NonNull IBinder windowToken);
+ public abstract boolean isPointInsideWindow(
+ @NonNull IBinder windowToken, int displayId, float displayX, float displayY);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 7b77fd0683cd..8f1f7ece897b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -172,6 +172,7 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.database.ContentObserver;
import android.graphics.Bitmap;
+import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
@@ -223,6 +224,7 @@ import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.MergedConfiguration;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseBooleanArray;
import android.util.TimeUtils;
@@ -7198,18 +7200,42 @@ public class WindowManagerService extends IWindowManager.Stub
private float mLatestMouseX;
private float mLatestMouseY;
- void updatePosition(float x, float y) {
+ /**
+ * The display that the pointer (mouse cursor) is currently shown on. This is updated
+ * directly by InputManagerService when the pointer display changes.
+ */
+ private int mPointerDisplayId = INVALID_DISPLAY;
+
+ /**
+ * Update the mouse cursor position as a result of a mouse movement.
+ * @return true if the position was successfully updated, false otherwise.
+ */
+ boolean updatePosition(int displayId, float x, float y) {
synchronized (this) {
mLatestEventWasMouse = true;
+
+ if (displayId != mPointerDisplayId) {
+ // The display of the position update does not match the display on which the
+ // mouse pointer is shown, so do not update the position.
+ return false;
+ }
mLatestMouseX = x;
mLatestMouseY = y;
+ return true;
+ }
+ }
+
+ void setPointerDisplayId(int displayId) {
+ synchronized (this) {
+ mPointerDisplayId = displayId;
}
}
@Override
public void onPointerEvent(MotionEvent motionEvent) {
if (motionEvent.isFromSource(InputDevice.SOURCE_MOUSE)) {
- updatePosition(motionEvent.getRawX(), motionEvent.getRawY());
+ updatePosition(motionEvent.getDisplayId(), motionEvent.getRawX(),
+ motionEvent.getRawY());
} else {
synchronized (this) {
mLatestEventWasMouse = false;
@@ -7219,6 +7245,7 @@ public class WindowManagerService extends IWindowManager.Stub
};
void updatePointerIcon(IWindow client) {
+ int pointerDisplayId;
float mouseX, mouseY;
synchronized(mMousePositionTracker) {
@@ -7227,6 +7254,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
mouseX = mMousePositionTracker.mLatestMouseX;
mouseY = mMousePositionTracker.mLatestMouseY;
+ pointerDisplayId = mMousePositionTracker.mPointerDisplayId;
}
synchronized (mGlobalLock) {
@@ -7243,6 +7271,10 @@ public class WindowManagerService extends IWindowManager.Stub
if (displayContent == null) {
return;
}
+ if (pointerDisplayId != displayContent.getDisplayId()) {
+ // Do not let the pointer icon be updated by a window on a different display.
+ return;
+ }
WindowState windowUnderPointer =
displayContent.getTouchableWinAtPointLocked(mouseX, mouseY);
if (windowUnderPointer != callingWin) {
@@ -7260,7 +7292,11 @@ public class WindowManagerService extends IWindowManager.Stub
void restorePointerIconLocked(DisplayContent displayContent, float latestX, float latestY) {
// Mouse position tracker has not been getting updates while dragging, update it now.
- mMousePositionTracker.updatePosition(latestX, latestY);
+ if (!mMousePositionTracker.updatePosition(
+ displayContent.getDisplayId(), latestX, latestY)) {
+ // The mouse position could not be updated, so ignore this request.
+ return;
+ }
WindowState windowUnderPointer =
displayContent.getTouchableWinAtPointLocked(latestX, latestY);
@@ -7284,6 +7320,10 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ void setMousePointerDisplayId(int displayId) {
+ mMousePositionTracker.setPointerDisplayId(displayId);
+ }
+
/**
* Update a tap exclude region in the window identified by the provided id. Touches down on this
* region will not:
@@ -7721,6 +7761,13 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
+ public Pair<Matrix, MagnificationSpec> getWindowTransformationMatrixAndMagnificationSpec(
+ IBinder token) {
+ return mAccessibilityController
+ .getWindowTransformationMatrixAndMagnificationSpec(token);
+ }
+
+ @Override
public void waitForAllWindowsDrawn(Runnable callback, long timeout, int displayId) {
final WindowContainer container = displayId == INVALID_DISPLAY
? mRoot : mRoot.getDisplayContent(displayId);
@@ -8193,23 +8240,15 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
- public void replaceInputSurfaceTouchableRegionWithWindowCrop(
- @NonNull SurfaceControl inputSurface,
- @NonNull InputWindowHandle inputWindowHandle,
- @NonNull IBinder windowToken) {
+ public boolean isPointInsideWindow(@NonNull IBinder windowToken, int displayId,
+ float displayX, float displayY) {
synchronized (mGlobalLock) {
final WindowState w = mWindowMap.get(windowToken);
- if (w == null) {
- return;
+ if (w == null || w.getDisplayId() != displayId) {
+ return false;
}
- // Make a copy of the InputWindowHandle to avoid leaking the window's
- // SurfaceControl.
- final InputWindowHandle localHandle = new InputWindowHandle(inputWindowHandle);
- localHandle.replaceTouchableRegionWithCrop(w.getSurfaceControl());
- final SurfaceControl.Transaction t = mTransactionFactory.get();
- t.setInputWindowInfo(inputSurface, localHandle);
- t.apply();
- t.close();
+
+ return w.getBounds().contains((int) displayX, (int) displayY);
}
}
}
@@ -8829,16 +8868,18 @@ public class WindowManagerService extends IWindowManager.Stub
WindowState newFocusTarget = displayContent == null
? null : displayContent.findFocusedWindow();
if (newFocusTarget == null) {
- ProtoLog.v(WM_DEBUG_FOCUS, "grantEmbeddedWindowFocus remove request for "
- + "win=%s dropped since no candidate was found",
+ t.setFocusedWindow(null, null, displayId).apply();
+ ProtoLog.v(WM_DEBUG_FOCUS, "grantEmbeddedWindowFocus win=%s"
+ + " dropped focus so setting focus to null since no candidate"
+ + " was found",
embeddedWindow);
return;
}
- t.requestFocusTransfer(newFocusTarget.mInputChannelToken, newFocusTarget.getName(),
- inputToken, embeddedWindow.toString(),
+ t.setFocusedWindow(newFocusTarget.mInputChannelToken, newFocusTarget.getName(),
displayId).apply();
+
EventLog.writeEvent(LOGTAG_INPUT_FOCUS,
- "Transfer focus request " + newFocusTarget,
+ "Focus request " + newFocusTarget,
"reason=grantEmbeddedWindowFocus(false)");
}
ProtoLog.v(WM_DEBUG_FOCUS, "grantEmbeddedWindowFocus win=%s grantFocus=%s",
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index cd19f64ba12e..3b282aada7ae 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3639,13 +3639,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
final int requested = mLastRequestedExclusionHeight[side];
final int granted = mLastGrantedExclusionHeight[side];
- final boolean inSplitScreen = getTask() != null && getTask().inSplitScreen();
-
FrameworkStatsLog.write(FrameworkStatsLog.EXCLUSION_RECT_STATE_CHANGED,
mAttrs.packageName, requested, requested - granted /* rejected */,
side + 1 /* Sides are 1-indexed in atoms.proto */,
(getConfiguration().orientation == ORIENTATION_LANDSCAPE),
- inSplitScreen, (int) duration);
+ false /* (deprecated param) inSplitscreen */, (int) duration);
}
private void initExclusionRestrictions() {
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index d2e56faa0914..ed9b2f072bff 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -378,7 +378,7 @@ class WindowToken extends WindowContainer<WindowState> {
SurfaceControl.Builder makeSurface() {
final SurfaceControl.Builder builder = super.makeSurface();
if (mRoundedCornerOverlay) {
- builder.setParent(null);
+ builder.setParent(getDisplayContent().getSurfaceControl());
}
return builder;
}
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index 49a4021b1a84..93152f2ea1b7 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -66,13 +66,13 @@ using android::base::unique_fd;
// Defines the maximum amount of VMAs we can send per process_madvise syscall.
// Currently this is set to UIO_MAXIOV which is the maximum segments allowed by
// iovec implementation used by process_madvise syscall
-#define MAX_VMAS_PER_BATCH UIO_MAXIOV
+#define MAX_VMAS_PER_COMPACTION UIO_MAXIOV
// Maximum bytes that we can send per process_madvise syscall once this limit
// is reached we split the remaining VMAs into another syscall. The MAX_RW_COUNT
// limit is imposed by iovec implementation. However, if you want to use a smaller
-// limit, it has to be a page aligned value.
-#define MAX_BYTES_PER_BATCH MAX_RW_COUNT
+// limit, it has to be a page aligned value, otherwise, compaction would fail.
+#define MAX_BYTES_PER_COMPACTION MAX_RW_COUNT
// Selected a high enough number to avoid clashing with linux errno codes
#define ERROR_COMPACTION_CANCELLED -1000
@@ -83,181 +83,6 @@ namespace android {
// before starting next VMA batch
static std::atomic<bool> cancelRunningCompaction;
-// A VmaBatch represents a set of VMAs that can be processed
-// as VMAs are processed by client code it is expected that the
-// VMAs get consumed which means they are discarded as they are
-// processed so that the first element always is the next element
-// to be sent
-struct VmaBatch {
- struct iovec* vmas;
- // total amount of VMAs to reach the end of iovec
- int totalVmas;
- // total amount of bytes that are remaining within iovec
- uint64_t totalBytes;
-};
-
-// Advances the iterator by the specified amount of bytes.
-// This is used to remove already processed or no longer
-// needed parts of the batch.
-// Returns total bytes consumed
-int consumeBytes(VmaBatch& batch, uint64_t bytesToConsume) {
- int index = 0;
- if (CC_UNLIKELY(bytesToConsume) < 0) {
- LOG(ERROR) << "Cannot consume negative bytes for VMA batch !";
- return 0;
- }
-
- if (bytesToConsume > batch.totalBytes) {
- // Avoid consuming more bytes than available
- bytesToConsume = batch.totalBytes;
- }
-
- uint64_t bytesConsumed = 0;
- while (bytesConsumed < bytesToConsume) {
- if (CC_UNLIKELY(index >= batch.totalVmas)) {
- // reach the end of the batch
- return bytesConsumed;
- }
- if (CC_UNLIKELY(bytesConsumed + batch.vmas[index].iov_len > bytesToConsume)) {
- // this is the whole VMA that will be consumed
- break;
- }
- bytesConsumed += batch.vmas[index].iov_len;
- batch.totalBytes -= batch.vmas[index].iov_len;
- --batch.totalVmas;
- ++index;
- }
-
- // Move pointer to consume all the whole VMAs
- batch.vmas = batch.vmas + index;
-
- // Consume the rest of the bytes partially at last VMA in batch
- uint64_t bytesLeftToConsume = bytesToConsume - bytesConsumed;
- bytesConsumed += bytesLeftToConsume;
- if (batch.totalVmas > 0) {
- batch.vmas[0].iov_base = (void*)((uint64_t)batch.vmas[0].iov_base + bytesLeftToConsume);
- }
-
- return bytesConsumed;
-}
-
-// given a source of vmas this class will act as a factory
-// of VmaBatch objects and it will allow generating batches
-// until there are no more left in the source vector.
-// Note: the class does not actually modify the given
-// vmas vector, instead it iterates on it until the end.
-class VmaBatchCreator {
- const std::vector<Vma>* sourceVmas;
- // This is the destination array where batched VMAs will be stored
- // it gets encapsulated into a VmaBatch which is the object
- // meant to be used by client code.
- struct iovec* destVmas;
-
- // Parameters to keep track of the iterator on the source vmas
- int currentIndex_;
- uint64_t currentOffset_;
-
-public:
- VmaBatchCreator(const std::vector<Vma>* vmasToBatch, struct iovec* destVmasVec)
- : sourceVmas(vmasToBatch), destVmas(destVmasVec), currentIndex_(0), currentOffset_(0) {}
-
- int currentIndex() { return currentIndex_; }
- uint64_t currentOffset() { return currentOffset_; }
-
- // Generates a batch and moves the iterator on the source vmas
- // past the last VMA in the batch.
- // Returns true on success, false on failure
- bool createNextBatch(VmaBatch& batch) {
- if (currentIndex_ >= MAX_VMAS_PER_BATCH && currentIndex_ >= sourceVmas->size()) {
- return false;
- }
-
- const std::vector<Vma>& vmas = *sourceVmas;
- batch.vmas = destVmas;
- uint64_t totalBytesInBatch = 0;
- int indexInBatch = 0;
-
- // Add VMAs to the batch up until we consumed all the VMAs or
- // reached any imposed limit of VMAs per batch.
- while (indexInBatch < MAX_VMAS_PER_BATCH && currentIndex_ < vmas.size()) {
- uint64_t vmaStart = vmas[currentIndex_].start + currentOffset_;
- uint64_t vmaSize = vmas[currentIndex_].end - vmaStart;
- if (CC_UNLIKELY(vmaSize == 0)) {
- // No more bytes to batch for this VMA, move to next one
- // this only happens if a batch partially consumed bytes
- // and offset landed at exactly the end of a vma
- continue;
- }
- batch.vmas[indexInBatch].iov_base = (void*)vmaStart;
- uint64_t bytesAvailableInBatch = MAX_BYTES_PER_BATCH - totalBytesInBatch;
-
- if (vmaSize >= bytesAvailableInBatch) {
- // VMA would exceed the max available bytes in batch
- // clamp with available bytes and finish batch.
- vmaSize = bytesAvailableInBatch;
- currentOffset_ += bytesAvailableInBatch;
- }
-
- batch.vmas[indexInBatch].iov_len = vmaSize;
- totalBytesInBatch += vmaSize;
-
- ++indexInBatch;
- if (totalBytesInBatch >= MAX_BYTES_PER_BATCH) {
- // Reached max bytes quota so this marks
- // the end of the batch
- break;
- }
-
- // Fully finished current VMA, move to next one
- currentOffset_ = 0;
- ++currentIndex_;
- }
- // Vmas where fully filled and we are past the last filled index.
- batch.totalVmas = indexInBatch;
- batch.totalBytes = totalBytesInBatch;
- return true;
- }
-};
-
-// Madvise a set of VMAs given in a batch for a specific process
-// The total number of bytes successfully madvised will be set on
-// outBytesProcessed.
-// Returns 0 on success and standard linux -errno code returned by
-// process_madvise on failure
-int madviseVmasFromBatch(unique_fd& pidfd, VmaBatch& batch, int madviseType,
- uint64_t* outBytesProcessed) {
- if (batch.totalVmas == 0) {
- // No VMAs in Batch, skip.
- *outBytesProcessed = 0;
- return 0;
- }
-
- ATRACE_BEGIN(StringPrintf("Madvise %d: %d VMAs", madviseType, batch.totalVmas).c_str());
- uint64_t bytesProcessedInSend =
- process_madvise(pidfd, batch.vmas, batch.totalVmas, madviseType, 0);
- ATRACE_END();
-
- if (CC_UNLIKELY(bytesProcessedInSend == -1)) {
- bytesProcessedInSend = 0;
- if (errno != EINVAL) {
- // Forward irrecoverable errors and bail out compaction
- *outBytesProcessed = 0;
- return -errno;
- }
- }
-
- if (bytesProcessedInSend < batch.totalBytes) {
- // Did not process all the bytes requested
- // skip last page which likely failed
- bytesProcessedInSend += PAGE_SIZE;
- }
-
- bytesProcessedInSend = consumeBytes(batch, bytesProcessedInSend);
-
- *outBytesProcessed = bytesProcessedInSend;
- return 0;
-}
-
// Legacy method for compacting processes, any new code should
// use compactProcess instead.
static inline void compactProcessProcfs(int pid, const std::string& compactionType) {
@@ -271,6 +96,8 @@ static inline void compactProcessProcfs(int pid, const std::string& compactionTy
// If any VMA fails compaction due to -EINVAL it will be skipped and continue.
// However, if it fails for any other reason, it will bail out and forward the error
static int64_t compactMemory(const std::vector<Vma>& vmas, int pid, int madviseType) {
+ static struct iovec vmasToKernel[MAX_VMAS_PER_COMPACTION];
+
if (vmas.empty()) {
return 0;
}
@@ -281,16 +108,13 @@ static int64_t compactMemory(const std::vector<Vma>& vmas, int pid, int madviseT
return -errno;
}
- struct iovec destVmas[MAX_VMAS_PER_BATCH];
-
- VmaBatch batch;
- VmaBatchCreator batcher(&vmas, destVmas);
-
int64_t totalBytesProcessed = 0;
- while (batcher.createNextBatch(batch)) {
- uint64_t bytesProcessedInSend;
- do {
+ int64_t vmaOffset = 0;
+ for (int iVma = 0; iVma < vmas.size();) {
+ uint64_t bytesSentToCompact = 0;
+ int iVec = 0;
+ while (iVec < MAX_VMAS_PER_COMPACTION && iVma < vmas.size()) {
if (CC_UNLIKELY(cancelRunningCompaction.load())) {
// There could be a significant delay between when a compaction
// is requested and when it is handled during this time our
@@ -300,13 +124,50 @@ static int64_t compactMemory(const std::vector<Vma>& vmas, int pid, int madviseT
StringPrintf("Cancelled compaction for %d", pid).c_str());
return ERROR_COMPACTION_CANCELLED;
}
- int error = madviseVmasFromBatch(pidfd, batch, madviseType, &bytesProcessedInSend);
- if (error < 0) {
- // Returns standard linux errno code
- return error;
+
+ uint64_t vmaStart = vmas[iVma].start + vmaOffset;
+ uint64_t vmaSize = vmas[iVma].end - vmaStart;
+ if (vmaSize == 0) {
+ goto next_vma;
+ }
+ vmasToKernel[iVec].iov_base = (void*)vmaStart;
+ if (vmaSize > MAX_BYTES_PER_COMPACTION - bytesSentToCompact) {
+ // Exceeded the max bytes that could be sent, so clamp
+ // the end to avoid exceeding limit and issue compaction
+ vmaSize = MAX_BYTES_PER_COMPACTION - bytesSentToCompact;
}
- totalBytesProcessed += bytesProcessedInSend;
- } while (batch.totalBytes > 0);
+
+ vmasToKernel[iVec].iov_len = vmaSize;
+ bytesSentToCompact += vmaSize;
+ ++iVec;
+ if (bytesSentToCompact >= MAX_BYTES_PER_COMPACTION) {
+ // Ran out of bytes within iovec, dispatch compaction.
+ vmaOffset += vmaSize;
+ break;
+ }
+
+ next_vma:
+ // Finished current VMA, and have more bytes remaining
+ vmaOffset = 0;
+ ++iVma;
+ }
+
+ ATRACE_BEGIN(StringPrintf("Compact %d VMAs", iVec).c_str());
+ auto bytesProcessed = process_madvise(pidfd, vmasToKernel, iVec, madviseType, 0);
+ ATRACE_END();
+
+ if (CC_UNLIKELY(bytesProcessed == -1)) {
+ if (errno == EINVAL) {
+ // This error is somewhat common due to an unevictable VMA if this is
+ // the case silently skip the bad VMA and continue compacting the rest.
+ continue;
+ } else {
+ // Forward irrecoverable errors and bail out compaction
+ return -errno;
+ }
+ }
+
+ totalBytesProcessed += bytesProcessed;
}
return totalBytesProcessed;
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 3c5ebe7c62ee..32adac7f282b 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -107,6 +107,7 @@ static struct {
jmethodID interceptKeyBeforeDispatching;
jmethodID dispatchUnhandledKey;
jmethodID checkInjectEventsPermission;
+ jmethodID onPointerDisplayIdChanged;
jmethodID onPointerDownOutsideFocus;
jmethodID getVirtualKeyQuietTimeMillis;
jmethodID getExcludedDeviceNames;
@@ -120,7 +121,6 @@ static struct {
jmethodID getLongPressTimeout;
jmethodID getPointerLayer;
jmethodID getPointerIcon;
- jmethodID getPointerDisplayId;
jmethodID getKeyboardLayoutOverlay;
jmethodID getDeviceAlias;
jmethodID getTouchCalibrationForInputDevice;
@@ -277,6 +277,7 @@ public:
void setFocusedDisplay(int32_t displayId);
void setInputDispatchMode(bool enabled, bool frozen);
void setSystemUiLightsOut(bool lightsOut);
+ void setPointerDisplayId(int32_t displayId);
void setPointerSpeed(int32_t speed);
void setPointerAcceleration(float acceleration);
void setInputDeviceEnabled(uint32_t deviceId, bool enabled);
@@ -288,7 +289,6 @@ public:
void requestPointerCapture(const sp<IBinder>& windowToken, bool enabled);
void setCustomPointerIcon(const SpriteIcon& icon);
void setMotionClassifierEnabled(bool enabled);
- void notifyPointerDisplayIdChanged();
/* --- InputReaderPolicyInterface implementation --- */
@@ -346,6 +346,7 @@ public:
std::map<int32_t, PointerAnimation>* outAnimationResources, int32_t displayId);
virtual int32_t getDefaultPointerIconId();
virtual int32_t getCustomPointerIconId();
+ virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos);
private:
sp<InputManagerInterface> mInputManager;
@@ -394,7 +395,6 @@ private:
void updateInactivityTimeoutLocked();
void handleInterceptActions(jint wmActions, nsecs_t when, uint32_t& policyFlags);
void ensureSpriteControllerLocked();
- int32_t getPointerDisplayId();
sp<SurfaceControl> getParentSurfaceForPointers(int displayId);
static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName);
@@ -498,13 +498,9 @@ void NativeInputManager::setDisplayViewports(JNIEnv* env, jobjectArray viewportO
}
}
- // Get the preferred pointer controller displayId.
- int32_t pointerDisplayId = getPointerDisplayId();
-
{ // acquire lock
AutoMutex _l(mLock);
mLocked.viewports = viewports;
- mLocked.pointerDisplayId = pointerDisplayId;
std::shared_ptr<PointerController> controller = mLocked.pointerController.lock();
if (controller != nullptr) {
controller->onDisplayViewportsUpdated(mLocked.viewports);
@@ -666,15 +662,12 @@ std::shared_ptr<PointerControllerInterface> NativeInputManager::obtainPointerCon
return controller;
}
-int32_t NativeInputManager::getPointerDisplayId() {
+void NativeInputManager::onPointerDisplayIdChanged(int32_t pointerDisplayId, float xPos,
+ float yPos) {
JNIEnv* env = jniEnv();
- jint pointerDisplayId = env->CallIntMethod(mServiceObj,
- gServiceClassInfo.getPointerDisplayId);
- if (checkAndClearExceptionFromCallback(env, "getPointerDisplayId")) {
- pointerDisplayId = ADISPLAY_ID_DEFAULT;
- }
-
- return pointerDisplayId;
+ env->CallVoidMethod(mServiceObj, gServiceClassInfo.onPointerDisplayIdChanged, pointerDisplayId,
+ xPos, yPos);
+ checkAndClearExceptionFromCallback(env, "onPointerDisplayIdChanged");
}
sp<SurfaceControl> NativeInputManager::getParentSurfaceForPointers(int displayId) {
@@ -1032,6 +1025,22 @@ void NativeInputManager::updateInactivityTimeoutLocked() REQUIRES(mLock) {
: InactivityTimeout::NORMAL);
}
+void NativeInputManager::setPointerDisplayId(int32_t displayId) {
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ if (mLocked.pointerDisplayId == displayId) {
+ return;
+ }
+
+ ALOGI("Setting pointer display id to %d.", displayId);
+ mLocked.pointerDisplayId = displayId;
+ } // release lock
+
+ mInputManager->getReader().requestRefreshConfiguration(
+ InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+}
+
void NativeInputManager::setPointerSpeed(int32_t speed) {
{ // acquire lock
AutoMutex _l(mLock);
@@ -1494,18 +1503,6 @@ void NativeInputManager::setMotionClassifierEnabled(bool enabled) {
mInputManager->getClassifier().setMotionClassifierEnabled(enabled);
}
-void NativeInputManager::notifyPointerDisplayIdChanged() {
- int32_t pointerDisplayId = getPointerDisplayId();
-
- { // acquire lock
- AutoMutex _l(mLock);
- mLocked.pointerDisplayId = pointerDisplayId;
- } // release lock
-
- mInputManager->getReader().requestRefreshConfiguration(
- InputReaderConfiguration::CHANGE_DISPLAY_INFO);
-}
-
// ----------------------------------------------------------------------------
static NativeInputManager* getNativeInputManager(JNIEnv* env, jobject clazz) {
@@ -2199,11 +2196,6 @@ static void nativeNotifyPortAssociationsChanged(JNIEnv* env, jobject nativeImplO
InputReaderConfiguration::CHANGE_DISPLAY_INFO);
}
-static void nativeNotifyPointerDisplayIdChanged(JNIEnv* env, jobject nativeImplObj) {
- NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- im->notifyPointerDisplayIdChanged();
-}
-
static void nativeSetDisplayEligibilityForPointerCapture(JNIEnv* env, jobject nativeImplObj,
jint displayId, jboolean isEligible) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -2321,6 +2313,11 @@ static void nativeCancelCurrentTouch(JNIEnv* env, jobject nativeImplObj) {
im->getInputManager()->getDispatcher().cancelCurrentTouch();
}
+static void nativeSetPointerDisplayId(JNIEnv* env, jobject nativeImplObj, jint displayId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+ im->setPointerDisplayId(displayId);
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod gInputManagerMethods[] = {
@@ -2393,7 +2390,6 @@ static const JNINativeMethod gInputManagerMethods[] = {
{"canDispatchToDisplay", "(II)Z", (void*)nativeCanDispatchToDisplay},
{"notifyPortAssociationsChanged", "()V", (void*)nativeNotifyPortAssociationsChanged},
{"changeUniqueIdAssociation", "()V", (void*)nativeChangeUniqueIdAssociation},
- {"notifyPointerDisplayIdChanged", "()V", (void*)nativeNotifyPointerDisplayIdChanged},
{"setDisplayEligibilityForPointerCapture", "(IZ)V",
(void*)nativeSetDisplayEligibilityForPointerCapture},
{"setMotionClassifierEnabled", "(Z)V", (void*)nativeSetMotionClassifierEnabled},
@@ -2403,6 +2399,7 @@ static const JNINativeMethod gInputManagerMethods[] = {
{"disableSensor", "(II)V", (void*)nativeDisableSensor},
{"flushSensor", "(II)Z", (void*)nativeFlushSensor},
{"cancelCurrentTouch", "()V", (void*)nativeCancelCurrentTouch},
+ {"setPointerDisplayId", "(I)V", (void*)nativeSetPointerDisplayId},
};
#define FIND_CLASS(var, className) \
@@ -2498,6 +2495,9 @@ int register_android_server_InputManager(JNIEnv* env) {
GET_METHOD_ID(gServiceClassInfo.checkInjectEventsPermission, clazz,
"checkInjectEventsPermission", "(II)Z");
+ GET_METHOD_ID(gServiceClassInfo.onPointerDisplayIdChanged, clazz, "onPointerDisplayIdChanged",
+ "(IFF)V");
+
GET_METHOD_ID(gServiceClassInfo.onPointerDownOutsideFocus, clazz,
"onPointerDownOutsideFocus", "(Landroid/os/IBinder;)V");
@@ -2537,9 +2537,6 @@ int register_android_server_InputManager(JNIEnv* env) {
GET_METHOD_ID(gServiceClassInfo.getPointerIcon, clazz,
"getPointerIcon", "(I)Landroid/view/PointerIcon;");
- GET_METHOD_ID(gServiceClassInfo.getPointerDisplayId, clazz,
- "getPointerDisplayId", "()I");
-
GET_METHOD_ID(gServiceClassInfo.getKeyboardLayoutOverlay, clazz,
"getKeyboardLayoutOverlay",
"(Landroid/hardware/input/InputDeviceIdentifier;)[Ljava/lang/String;");
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 8a7134e24cf8..7f466f38e96a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -8513,6 +8513,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
}
+ private boolean isDeviceOwnerUserId(int userId) {
+ synchronized (getLockObject()) {
+ return mOwners.getDeviceOwnerComponent() != null
+ && mOwners.getDeviceOwnerUserId() == userId;
+ }
+ }
+
private boolean isDeviceOwnerPackage(String packageName, int userId) {
synchronized (getLockObject()) {
return mOwners.hasDeviceOwner()
@@ -12117,10 +12124,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return;
}
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(isProfileOwner(caller)
+ Preconditions.checkCallAuthorization((isProfileOwner(caller)
+ && isManagedProfile(caller.getUserId()))
|| isDefaultDeviceOwner(caller),
- "Caller is not profile owner or device owner;"
- + " only profile owner or device owner may control the preferential"
+ "Caller is not managed profile owner or device owner;"
+ + " only managed profile owner or device owner may control the preferential"
+ " network service");
synchronized (getLockObject()) {
final ActiveAdmin requiredAdmin = getDeviceOrProfileOwnerAdminLocked(
@@ -12147,11 +12155,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(isProfileOwner(caller)
+ Preconditions.checkCallAuthorization((isProfileOwner(caller)
+ && isManagedProfile(caller.getUserId()))
|| isDefaultDeviceOwner(caller),
- "Caller is not profile owner or device owner;"
- + " only profile owner or device owner may retrieve the preferential"
- + " network service configurations");
+ "Caller is not managed profile owner or device owner;"
+ + " only managed profile owner or device owner may retrieve the "
+ + "preferential network service configurations");
synchronized (getLockObject()) {
final ActiveAdmin requiredAdmin = getDeviceOrProfileOwnerAdminLocked(
caller.getUserId());
@@ -18266,7 +18275,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
private void updateNetworkPreferenceForUser(int userId,
List<PreferentialNetworkServiceConfig> preferentialNetworkServiceConfigs) {
- if (!isManagedProfile(userId)) {
+ if (!isManagedProfile(userId) && !isDeviceOwnerUserId(userId)) {
return;
}
List<ProfileNetworkPreference> preferences = new ArrayList<>();
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index 8aa9f60e922d..994a76700dc0 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -238,7 +238,7 @@ public class MidiService extends IMidiManager.Stub {
}
}
- // called from Device.close()
+ // called from Device.closeLocked()
public void removeDeviceConnection(DeviceConnection connection) {
mDeviceConnections.remove(connection.getToken());
if (mListeners.size() == 0 && mDeviceConnections.size() == 0) {
@@ -294,12 +294,6 @@ public class MidiService extends IMidiManager.Stub {
}
for (DeviceConnection connection : mDeviceConnections.values()) {
- if (connection.getDevice().getDeviceInfo().getType()
- == MidiDeviceInfo.TYPE_USB) {
- synchronized (mUsbMidiLock) {
- removeUsbMidiDeviceLocked(connection.getDevice().getDeviceInfo());
- }
- }
connection.getDevice().removeDeviceConnection(connection);
}
}
@@ -541,6 +535,13 @@ public class MidiService extends IMidiManager.Stub {
synchronized (mDeviceConnections) {
mDeviceConnections.remove(connection);
+ if (connection.getDevice().getDeviceInfo().getType()
+ == MidiDeviceInfo.TYPE_USB) {
+ synchronized (mUsbMidiLock) {
+ removeUsbMidiDeviceLocked(connection.getDevice().getDeviceInfo());
+ }
+ }
+
if (mDeviceConnections.size() == 0 && mServiceConnection != null) {
mContext.unbindService(mServiceConnection);
mServiceConnection = null;
@@ -559,6 +560,12 @@ public class MidiService extends IMidiManager.Stub {
public void closeLocked() {
synchronized (mDeviceConnections) {
for (DeviceConnection connection : mDeviceConnections) {
+ if (connection.getDevice().getDeviceInfo().getType()
+ == MidiDeviceInfo.TYPE_USB) {
+ synchronized (mUsbMidiLock) {
+ removeUsbMidiDeviceLocked(connection.getDevice().getDeviceInfo());
+ }
+ }
connection.getClient().removeDeviceConnection(connection);
}
mDeviceConnections.clear();
@@ -1401,6 +1408,8 @@ public class MidiService extends IMidiManager.Stub {
String deviceName = extractUsbDeviceName(name);
String tagName = extractUsbDeviceTag(name);
+ Log.i(TAG, "Checking " + deviceName + " " + tagName);
+
// Only one MIDI 2.0 device can be used at once.
// Multiple MIDI 1.0 devices can be used at once.
if (mUsbMidiUniversalDeviceInUse.contains(deviceName)
@@ -1420,6 +1429,8 @@ public class MidiService extends IMidiManager.Stub {
String deviceName = extractUsbDeviceName(name);
String tagName = extractUsbDeviceTag(name);
+ Log.i(TAG, "Adding " + deviceName + " " + tagName);
+
if ((tagName).equals(MIDI_UNIVERSAL_STRING)) {
mUsbMidiUniversalDeviceInUse.add(deviceName);
} else if ((tagName).equals(MIDI_LEGACY_STRING)) {
@@ -1437,6 +1448,8 @@ public class MidiService extends IMidiManager.Stub {
String deviceName = extractUsbDeviceName(name);
String tagName = extractUsbDeviceTag(name);
+ Log.i(TAG, "Removing " + deviceName + " " + tagName);
+
if ((tagName).equals(MIDI_UNIVERSAL_STRING)) {
mUsbMidiUniversalDeviceInUse.remove(deviceName);
} else if ((tagName).equals(MIDI_LEGACY_STRING)) {
diff --git a/services/proguard.flags b/services/proguard.flags
index 425da6c11177..bad02b47031c 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -33,6 +33,11 @@
public <init>(...);
}
+# Accessed from com.android.compos APEX
+-keep,allowoptimization,allowaccessmodification class com.android.internal.art.ArtStatsLog {
+ public static void write(...);
+}
+
# Binder interfaces
-keep,allowoptimization,allowaccessmodification class * extends android.os.IInterface
-keep,allowoptimization,allowaccessmodification class * extends android.os.IHwInterface
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 2f68306e9ba1..8d59dce29bd9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -30,6 +30,7 @@ import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND;
import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT;
import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT_UI;
import static android.app.ActivityManager.PROCESS_STATE_RECEIVER;
import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
@@ -1591,6 +1592,69 @@ public class MockingOomAdjusterTests {
@SuppressWarnings("GuardedBy")
@Test
+ public void testUpdateOomAdj_DoOne_TreatLikeVisFGS() {
+ final ProcessRecord app1 = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+ MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
+ final ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
+ MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
+ final ProcessRecord client1 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
+ MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
+ final ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
+ MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
+ client1.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+ client2.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+
+ final ServiceRecord s1 = bindService(app1, client1, null,
+ Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE, mock(IBinder.class));
+ final ServiceRecord s2 = bindService(app2, client2, null,
+ Context.BIND_IMPORTANT, mock(IBinder.class));
+
+ sService.mOomAdjuster.updateOomAdjLocked(app1, OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+
+ assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, VISIBLE_APP_ADJ,
+ SCHED_GROUP_DEFAULT);
+ assertProcStates(app2, PROCESS_STATE_PERSISTENT, PERSISTENT_SERVICE_ADJ,
+ SCHED_GROUP_DEFAULT);
+
+ bindService(app2, client1, s2, Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE,
+ mock(IBinder.class));
+ sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+ assertProcStates(app2, PROCESS_STATE_PERSISTENT, PERSISTENT_SERVICE_ADJ,
+ SCHED_GROUP_DEFAULT);
+
+ s1.getConnections().clear();
+ s2.getConnections().clear();
+ client1.mState.setMaxAdj(UNKNOWN_ADJ);
+ client2.mState.setMaxAdj(UNKNOWN_ADJ);
+ client1.mServices.setHasForegroundServices(true, 0);
+ client2.mState.setHasOverlayUi(true);
+
+ bindService(app1, client1, s1, Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE,
+ mock(IBinder.class));
+ bindService(app2, client2, s2, Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE,
+ mock(IBinder.class));
+
+ sService.mOomAdjuster.updateOomAdjLocked(app1, OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+
+ assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
+ SCHED_GROUP_DEFAULT);
+ assertProcStates(app2, PROCESS_STATE_IMPORTANT_FOREGROUND, PERCEPTIBLE_APP_ADJ,
+ SCHED_GROUP_DEFAULT);
+
+ client2.mState.setHasOverlayUi(false);
+ doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
+ doReturn(client2).when(sService).getTopApp();
+ sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+
+ sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+ assertProcStates(app2, PROCESS_STATE_BOUND_TOP, VISIBLE_APP_ADJ,
+ SCHED_GROUP_DEFAULT);
+ }
+
+ @SuppressWarnings("GuardedBy")
+ @Test
public void testUpdateOomAdj_UidIdle_StopService() {
final ProcessRecord app1 = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index 08df4381ad62..19df5a2ddd8b 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -89,8 +89,10 @@ import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteCallback;
import android.os.RemoteException;
+import android.util.Pair;
import android.view.Display;
import android.view.KeyEvent;
+import android.view.MagnificationSpec;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
import android.view.accessibility.IAccessibilityInteractionConnection;
@@ -145,6 +147,8 @@ public class AbstractAccessibilityServiceConnectionTest {
private static final int USER_ID = 1;
private static final int USER_ID2 = 2;
private static final int INTERACTION_ID = 199;
+ private static final Pair<float[], MagnificationSpec> FAKE_MATRIX_AND_MAG_SPEC =
+ new Pair<>(new float[9], new MagnificationSpec());
private static final int PID = Process.myPid();
private static final long TID = Process.myTid();
private static final int UID = Process.myUid();
@@ -188,6 +192,8 @@ public class AbstractAccessibilityServiceConnectionTest {
.thenReturn(mMockFingerprintGestureDispatcher);
when(mMockSystemSupport.getMagnificationProcessor())
.thenReturn(mMockMagnificationProcessor);
+ when(mMockSystemSupport.getWindowTransformationMatrixAndMagnificationSpec(anyInt()))
+ .thenReturn(FAKE_MATRIX_AND_MAG_SPEC);
PowerManager powerManager =
new PowerManager(mMockContext, mMockIPowerManager, mMockIThermalService, mHandler);
diff --git a/services/tests/servicestests/src/com/android/server/am/DropboxRateLimiterTest.java b/services/tests/servicestests/src/com/android/server/am/DropboxRateLimiterTest.java
index d1390c68e130..e68a8a0f3af8 100644
--- a/services/tests/servicestests/src/com/android/server/am/DropboxRateLimiterTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/DropboxRateLimiterTest.java
@@ -49,10 +49,11 @@ public class DropboxRateLimiterTest {
assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit());
assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit());
assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit());
+ assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit());
// Different processes and tags should not get rate limited either.
assertFalse(mRateLimiter.shouldRateLimit("tag", "process2").shouldRateLimit());
assertFalse(mRateLimiter.shouldRateLimit("tag2", "process").shouldRateLimit());
- // The 6th entry of the same process should be rate limited.
+ // The 7th entry of the same process should be rate limited.
assertTrue(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit());
}
@@ -64,12 +65,13 @@ public class DropboxRateLimiterTest {
assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit());
assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit());
assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit());
- // The 6th entry of the same process should be rate limited.
+ assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit());
+ // The 7th entry of the same process should be rate limited.
assertTrue(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit());
- // After 11 seconds there should be nothing left in the buffer and the same type of entry
+ // After 11 minutes there should be nothing left in the buffer and the same type of entry
// should not get rate limited anymore.
- mClock.setOffsetMillis(11000);
+ mClock.setOffsetMillis(11 * 60 * 1000);
assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit());
}
@@ -86,13 +88,15 @@ public class DropboxRateLimiterTest {
mRateLimiter.shouldRateLimit("tag", "p").droppedCountSinceRateLimitActivated());
assertEquals(0,
mRateLimiter.shouldRateLimit("tag", "p").droppedCountSinceRateLimitActivated());
+ assertEquals(0,
+ mRateLimiter.shouldRateLimit("tag", "p").droppedCountSinceRateLimitActivated());
assertEquals(1,
mRateLimiter.shouldRateLimit("tag", "p").droppedCountSinceRateLimitActivated());
assertEquals(2,
mRateLimiter.shouldRateLimit("tag", "p").droppedCountSinceRateLimitActivated());
- // After 11 seconds the rate limiting buffer will be cleared and rate limiting will stop.
- mClock.setOffsetMillis(11000);
+ // After 11 minutes the rate limiting buffer will be cleared and rate limiting will stop.
+ mClock.setOffsetMillis(11 * 60 * 1000);
// The first call after rate limiting stops will still return the number of dropped events.
assertEquals(2,
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 b4bb04d2b1b4..77cbb3a6398c 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
@@ -19,22 +19,22 @@ package com.android.server.companion.virtual;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.IInputManager;
-import android.hardware.input.InputManager;
import android.hardware.input.InputManagerInternal;
import android.os.Binder;
+import android.os.Handler;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
import android.view.Display;
import android.view.DisplayInfo;
-import androidx.test.runner.AndroidJUnit4;
-
import com.android.server.LocalServices;
import org.junit.Before;
@@ -44,7 +44,8 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@Presubmit
-@RunWith(AndroidJUnit4.class)
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class InputControllerTest {
@Mock
@@ -56,13 +57,16 @@ public class InputControllerTest {
@Mock
private IInputManager mIInputManagerMock;
+ private InputManagerMockHelper mInputManagerMockHelper;
private InputController mInputController;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ mInputManagerMockHelper = new InputManagerMockHelper(
+ TestableLooper.get(this), mNativeWrapperMock, mIInputManagerMock);
- doNothing().when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
+ doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
LocalServices.removeServiceForTest(InputManagerInternal.class);
LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
@@ -72,10 +76,10 @@ public class InputControllerTest {
LocalServices.removeServiceForTest(DisplayManagerInternal.class);
LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
- InputManager.resetInstance(mIInputManagerMock);
- doNothing().when(mIInputManagerMock).addUniqueIdAssociation(anyString(), anyString());
- doNothing().when(mIInputManagerMock).removeUniqueIdAssociation(anyString());
- mInputController = new InputController(new Object(), mNativeWrapperMock);
+ // Allow virtual devices to be created on the looper thread for testing.
+ final InputController.DeviceCreationThreadVerifier threadVerifier = () -> true;
+ mInputController = new InputController(new Object(), mNativeWrapperMock,
+ new Handler(TestableLooper.get(this).getLooper()), threadVerifier);
}
@Test
@@ -83,6 +87,7 @@ public class InputControllerTest {
final IBinder deviceToken = new Binder();
mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken,
/* displayId= */ 1);
+ verify(mNativeWrapperMock).openUinputMouse(eq("name"), eq(1), eq(1), anyString());
verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
doReturn(1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
mInputController.unregisterInputDevice(deviceToken);
@@ -95,10 +100,12 @@ public class InputControllerTest {
final IBinder deviceToken = new Binder();
mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken,
/* displayId= */ 1);
+ verify(mNativeWrapperMock).openUinputMouse(eq("name"), eq(1), eq(1), anyString());
verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
final IBinder deviceToken2 = new Binder();
mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken2,
/* displayId= */ 2);
+ verify(mNativeWrapperMock, times(2)).openUinputMouse(eq("name"), eq(1), eq(1), anyString());
verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(2));
mInputController.unregisterInputDevice(deviceToken);
verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
new file mode 100644
index 000000000000..aa2d97e38928
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
@@ -0,0 +1,101 @@
+/*
+ * 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.companion.virtual;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.notNull;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.when;
+
+import android.hardware.input.IInputDevicesChangedListener;
+import android.hardware.input.IInputManager;
+import android.hardware.input.InputManager;
+import android.os.RemoteException;
+import android.testing.TestableLooper;
+import android.view.InputDevice;
+
+import org.mockito.invocation.InvocationOnMock;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.IntStream;
+
+/**
+ * A test utility class used to share the logic for setting up {@link InputManager}'s callback for
+ * when a virtual input device being added.
+ */
+class InputManagerMockHelper {
+ private final TestableLooper mTestableLooper;
+ private final InputController.NativeWrapper mNativeWrapperMock;
+ private final IInputManager mIInputManagerMock;
+ private final List<InputDevice> mDevices = new ArrayList<>();
+ private IInputDevicesChangedListener mDevicesChangedListener;
+
+ InputManagerMockHelper(TestableLooper testableLooper,
+ InputController.NativeWrapper nativeWrapperMock, IInputManager iInputManagerMock)
+ throws Exception {
+ mTestableLooper = testableLooper;
+ mNativeWrapperMock = nativeWrapperMock;
+ mIInputManagerMock = iInputManagerMock;
+
+ doAnswer(this::handleNativeOpenInputDevice).when(mNativeWrapperMock).openUinputMouse(
+ anyString(), anyInt(), anyInt(), anyString());
+ doAnswer(this::handleNativeOpenInputDevice).when(mNativeWrapperMock).openUinputKeyboard(
+ anyString(), anyInt(), anyInt(), anyString());
+ doAnswer(this::handleNativeOpenInputDevice).when(mNativeWrapperMock).openUinputTouchscreen(
+ anyString(), anyInt(), anyInt(), anyString(), anyInt(), anyInt());
+
+ doAnswer(inv -> {
+ mDevicesChangedListener = inv.getArgument(0);
+ return null;
+ }).when(mIInputManagerMock).registerInputDevicesChangedListener(notNull());
+ when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
+ doAnswer(inv -> mDevices.get(inv.getArgument(0)))
+ .when(mIInputManagerMock).getInputDevice(anyInt());
+ doNothing().when(mIInputManagerMock).addUniqueIdAssociation(anyString(), anyString());
+ doNothing().when(mIInputManagerMock).removeUniqueIdAssociation(anyString());
+
+ // Set a new instance of InputManager for testing that uses the IInputManager mock as the
+ // interface to the server.
+ InputManager.resetInstance(mIInputManagerMock);
+ }
+
+ private Void handleNativeOpenInputDevice(InvocationOnMock inv) {
+ Objects.requireNonNull(mDevicesChangedListener,
+ "InputController did not register an InputDevicesChangedListener.");
+ // We only use a subset of the fields of InputDevice in InputController.
+ final InputDevice device = new InputDevice(mDevices.size() /*id*/, 1 /*generation*/, 0,
+ inv.getArgument(0) /*name*/, inv.getArgument(1) /*vendorId*/,
+ inv.getArgument(2) /*productId*/, inv.getArgument(3) /*descriptor*/,
+ true /*isExternal*/, 0 /*sources*/, 0 /*keyboardType*/,
+ null /*keyCharacterMap*/, false /*hasVibrator*/, false /*hasMic*/,
+ false /*hasButtonUnderPad*/, false /*hasSensor*/, false /*hasBattery*/);
+ mDevices.add(device);
+ try {
+ mDevicesChangedListener.onInputDevicesChanged(
+ mDevices.stream().flatMapToInt(
+ d -> IntStream.of(d.getId(), d.getGeneration())).toArray());
+ } catch (RemoteException ignored) {
+ }
+ // Process the device added notification.
+ mTestableLooper.processAllMessages();
+ return null;
+ }
+}
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 808f8c2cc626..cbb9fd7c30dd 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
@@ -54,6 +54,7 @@ import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.graphics.Point;
import android.hardware.display.DisplayManagerInternal;
+import android.hardware.input.IInputManager;
import android.hardware.input.InputManagerInternal;
import android.hardware.input.VirtualKeyEvent;
import android.hardware.input.VirtualMouseButtonEvent;
@@ -118,6 +119,7 @@ public class VirtualDeviceManagerServiceTest {
private static final int FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES = 0x00000;
private Context mContext;
+ private InputManagerMockHelper mInputManagerMockHelper;
private VirtualDeviceImpl mDeviceImpl;
private InputController mInputController;
private AssociationInfo mAssociationInfo;
@@ -146,6 +148,8 @@ public class VirtualDeviceManagerServiceTest {
private IAudioConfigChangedCallback mConfigChangedCallback;
@Mock
private ApplicationInfo mApplicationInfoMock;
+ @Mock
+ IInputManager mIInputManagerMock;
private ArraySet<ComponentName> getBlockedActivities() {
ArraySet<ComponentName> blockedActivities = new ArraySet<>();
@@ -170,13 +174,13 @@ public class VirtualDeviceManagerServiceTest {
}
@Before
- public void setUp() {
+ public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
LocalServices.removeServiceForTest(DisplayManagerInternal.class);
LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
- doNothing().when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
+ doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
doNothing().when(mInputManagerInternalMock).setPointerAcceleration(anyFloat(), anyInt());
doNothing().when(mInputManagerInternalMock).setPointerIconVisible(anyBoolean(), anyInt());
LocalServices.removeServiceForTest(InputManagerInternal.class);
@@ -199,7 +203,13 @@ public class VirtualDeviceManagerServiceTest {
new Handler(TestableLooper.get(this).getLooper()));
when(mContext.getSystemService(Context.POWER_SERVICE)).thenReturn(mPowerManager);
- mInputController = new InputController(new Object(), mNativeWrapperMock);
+ mInputManagerMockHelper = new InputManagerMockHelper(
+ TestableLooper.get(this), mNativeWrapperMock, mIInputManagerMock);
+ // Allow virtual devices to be created on the looper thread for testing.
+ final InputController.DeviceCreationThreadVerifier threadVerifier = () -> true;
+ mInputController = new InputController(new Object(), mNativeWrapperMock,
+ new Handler(TestableLooper.get(this).getLooper()), threadVerifier);
+
mAssociationInfo = new AssociationInfo(1, 0, null,
MacAddress.BROADCAST_ADDRESS, "", null, true, false, 0, 0);
diff --git a/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt b/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt
new file mode 100644
index 000000000000..e78f0c77d6b3
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt
@@ -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.server.input
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.hardware.display.DisplayViewport
+import android.hardware.input.InputManagerInternal
+import android.os.IInputConstants
+import android.os.test.TestLooper
+import android.platform.test.annotations.Presubmit
+import android.view.Display
+import android.view.PointerIcon
+import androidx.test.InstrumentationRegistry
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.junit.MockitoJUnit
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+/**
+ * Tests for {@link InputManagerService}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:InputManagerServiceTests
+ */
+@Presubmit
+class InputManagerServiceTests {
+
+ @get:Rule
+ val rule = MockitoJUnit.rule()!!
+
+ @Mock
+ private lateinit var native: NativeInputManagerService
+
+ @Mock
+ private lateinit var wmCallbacks: InputManagerService.WindowManagerCallbacks
+
+ private lateinit var service: InputManagerService
+ private lateinit var localService: InputManagerInternal
+ private lateinit var context: Context
+ private lateinit var testLooper: TestLooper
+
+ @Before
+ fun setup() {
+ context = spy(ContextWrapper(InstrumentationRegistry.getContext()))
+ testLooper = TestLooper()
+ service =
+ InputManagerService(object : InputManagerService.Injector(context, testLooper.looper) {
+ override fun getNativeService(
+ service: InputManagerService?
+ ): NativeInputManagerService {
+ return native
+ }
+
+ override fun registerLocalService(service: InputManagerInternal?) {
+ localService = service!!
+ }
+ })
+ assertTrue("Local service must be registered", this::localService.isInitialized)
+ service.setWindowManagerCallbacks(wmCallbacks)
+ }
+
+ @Test
+ fun testPointerDisplayUpdatesWhenDisplayViewportsChanged() {
+ val displayId = 123
+ `when`(wmCallbacks.pointerDisplayId).thenReturn(displayId)
+ val viewports = listOf<DisplayViewport>()
+ localService.setDisplayViewports(viewports)
+ verify(native).setDisplayViewports(any(Array<DisplayViewport>::class.java))
+ verify(native).setPointerDisplayId(displayId)
+
+ val x = 42f
+ val y = 314f
+ service.onPointerDisplayIdChanged(displayId, x, y)
+ testLooper.dispatchNext()
+ verify(wmCallbacks).notifyPointerDisplayIdChanged(displayId, x, y)
+ }
+
+ @Test
+ fun testSetVirtualMousePointerDisplayId() {
+ // Set the virtual mouse pointer displayId, and ensure that the calling thread is blocked
+ // until the native callback happens.
+ var countDownLatch = CountDownLatch(1)
+ val overrideDisplayId = 123
+ Thread {
+ assertTrue("Setting virtual pointer display should succeed",
+ localService.setVirtualMousePointerDisplayId(overrideDisplayId))
+ countDownLatch.countDown()
+ }.start()
+ assertFalse("Setting virtual pointer display should block",
+ countDownLatch.await(100, TimeUnit.MILLISECONDS))
+
+ val x = 42f
+ val y = 314f
+ service.onPointerDisplayIdChanged(overrideDisplayId, x, y)
+ testLooper.dispatchNext()
+ verify(wmCallbacks).notifyPointerDisplayIdChanged(overrideDisplayId, x, y)
+ assertTrue("Native callback unblocks calling thread",
+ countDownLatch.await(100, TimeUnit.MILLISECONDS))
+ verify(native).setPointerDisplayId(overrideDisplayId)
+
+ // Ensure that setting the same override again succeeds immediately.
+ assertTrue("Setting the same virtual mouse pointer displayId again should succeed",
+ localService.setVirtualMousePointerDisplayId(overrideDisplayId))
+
+ // Ensure that we did not query WM for the pointerDisplayId when setting the override
+ verify(wmCallbacks, never()).pointerDisplayId
+
+ // Unset the virtual mouse pointer displayId, and ensure that we query WM for the new
+ // pointer displayId and the calling thread is blocked until the native callback happens.
+ countDownLatch = CountDownLatch(1)
+ val pointerDisplayId = 42
+ `when`(wmCallbacks.pointerDisplayId).thenReturn(pointerDisplayId)
+ Thread {
+ assertTrue("Unsetting virtual mouse pointer displayId should succeed",
+ localService.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY))
+ countDownLatch.countDown()
+ }.start()
+ assertFalse("Unsetting virtual mouse pointer displayId should block",
+ countDownLatch.await(100, TimeUnit.MILLISECONDS))
+
+ service.onPointerDisplayIdChanged(pointerDisplayId, x, y)
+ testLooper.dispatchNext()
+ verify(wmCallbacks).notifyPointerDisplayIdChanged(pointerDisplayId, x, y)
+ assertTrue("Native callback unblocks calling thread",
+ countDownLatch.await(100, TimeUnit.MILLISECONDS))
+ verify(native).setPointerDisplayId(pointerDisplayId)
+ }
+
+ @Test
+ fun testSetVirtualMousePointerDisplayId_unsuccessfulUpdate() {
+ // Set the virtual mouse pointer displayId, and ensure that the calling thread is blocked
+ // until the native callback happens.
+ val countDownLatch = CountDownLatch(1)
+ val overrideDisplayId = 123
+ Thread {
+ assertFalse("Setting virtual pointer display should be unsuccessful",
+ localService.setVirtualMousePointerDisplayId(overrideDisplayId))
+ countDownLatch.countDown()
+ }.start()
+ assertFalse("Setting virtual pointer display should block",
+ countDownLatch.await(100, TimeUnit.MILLISECONDS))
+
+ val x = 42f
+ val y = 314f
+ // Assume the native callback updates the pointerDisplayId to the incorrect value.
+ service.onPointerDisplayIdChanged(Display.INVALID_DISPLAY, x, y)
+ testLooper.dispatchNext()
+ verify(wmCallbacks).notifyPointerDisplayIdChanged(Display.INVALID_DISPLAY, x, y)
+ assertTrue("Native callback unblocks calling thread",
+ countDownLatch.await(100, TimeUnit.MILLISECONDS))
+ verify(native).setPointerDisplayId(overrideDisplayId)
+ }
+
+ @Test
+ fun testSetVirtualMousePointerDisplayId_competingRequests() {
+ val firstRequestSyncLatch = CountDownLatch(1)
+ doAnswer {
+ firstRequestSyncLatch.countDown()
+ }.`when`(native).setPointerDisplayId(anyInt())
+
+ val firstRequestLatch = CountDownLatch(1)
+ val firstOverride = 123
+ Thread {
+ assertFalse("Setting virtual pointer display from thread 1 should be unsuccessful",
+ localService.setVirtualMousePointerDisplayId(firstOverride))
+ firstRequestLatch.countDown()
+ }.start()
+ assertFalse("Setting virtual pointer display should block",
+ firstRequestLatch.await(100, TimeUnit.MILLISECONDS))
+
+ assertTrue("Wait for first thread's request should succeed",
+ firstRequestSyncLatch.await(100, TimeUnit.MILLISECONDS))
+
+ val secondRequestLatch = CountDownLatch(1)
+ val secondOverride = 42
+ Thread {
+ assertTrue("Setting virtual mouse pointer from thread 2 should be successful",
+ localService.setVirtualMousePointerDisplayId(secondOverride))
+ secondRequestLatch.countDown()
+ }.start()
+ assertFalse("Setting virtual mouse pointer should block",
+ secondRequestLatch.await(100, TimeUnit.MILLISECONDS))
+
+ val x = 42f
+ val y = 314f
+ // Assume the native callback updates directly to the second request.
+ service.onPointerDisplayIdChanged(secondOverride, x, y)
+ testLooper.dispatchNext()
+ verify(wmCallbacks).notifyPointerDisplayIdChanged(secondOverride, x, y)
+ assertTrue("Native callback unblocks first thread",
+ firstRequestLatch.await(100, TimeUnit.MILLISECONDS))
+ assertTrue("Native callback unblocks second thread",
+ secondRequestLatch.await(100, TimeUnit.MILLISECONDS))
+ verify(native, times(2)).setPointerDisplayId(anyInt())
+ }
+
+ @Test
+ fun onDisplayRemoved_resetAllAdditionalInputProperties() {
+ localService.setVirtualMousePointerDisplayId(10)
+ localService.setPointerIconVisible(false, 10)
+ verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL))
+ localService.setPointerAcceleration(5f, 10)
+ verify(native).setPointerAcceleration(eq(5f))
+
+ service.onDisplayRemoved(10)
+ verify(native).displayRemoved(eq(10))
+ verify(native).setPointerIconType(eq(PointerIcon.TYPE_NOT_SPECIFIED))
+ verify(native).setPointerAcceleration(
+ eq(IInputConstants.DEFAULT_POINTER_ACCELERATION.toFloat()))
+
+ localService.setVirtualMousePointerDisplayId(10)
+ verify(native).setPointerDisplayId(eq(10))
+ verifyNoMoreInteractions(native)
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
index a227cd3c6f5c..035249e32d74 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -70,6 +70,7 @@ import com.google.common.collect.ImmutableMap;
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -516,6 +517,7 @@ public class RecoverableKeyStoreManagerTest {
}
}
+ @Ignore("Causing breakages so ignoring to resolve, b/231667368")
@Test
public void initRecoveryService_alwaysUpdatesCertsWhenTestRootCertIsUsed() throws Exception {
int uid = Binder.getCallingUid();
@@ -539,6 +541,7 @@ public class RecoverableKeyStoreManagerTest {
testRootCertAlias)).isEqualTo(TestData.getInsecureCertPathForEndpoint2());
}
+ @Ignore("Causing breakages so ignoring to resolve, b/231667368")
@Test
public void initRecoveryService_updatesCertsIndependentlyForDifferentRoots() throws Exception {
int uid = Binder.getCallingUid();
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 40943774c0af..fdf9354747a0 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -1975,7 +1975,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
if (si == null) {
return null;
}
- mService.waitForBitmapSaves();
+ mService.waitForBitmapSavesForTest();
return new File(si.getBitmapPath()).getName();
}
@@ -1984,7 +1984,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
if (si == null) {
return null;
}
- mService.waitForBitmapSaves();
+ mService.waitForBitmapSavesForTest();
return new File(si.getBitmapPath()).getAbsolutePath();
}
@@ -2139,7 +2139,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
}
protected boolean bitmapDirectoryExists(String packageName, int userId) {
- mService.waitForBitmapSaves();
+ mService.waitForBitmapSavesForTest();
final File path = new File(mService.getUserBitmapFilePath(userId), packageName);
return path.isDirectory();
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 411b52155abb..867890f938ba 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -1040,7 +1040,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
dumpsysOnLogcat();
- mService.waitForBitmapSaves();
+ mService.waitForBitmapSavesForTest();
// Check files and directories.
// Package 3 has no bitmaps, so we don't create a directory.
assertBitmapDirectories(USER_0, CALLING_PACKAGE_1, CALLING_PACKAGE_2);
@@ -1096,7 +1096,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
makeFile(mService.getUserBitmapFilePath(USER_10), CALLING_PACKAGE_2, "3").createNewFile();
makeFile(mService.getUserBitmapFilePath(USER_10), CALLING_PACKAGE_2, "4").createNewFile();
- mService.waitForBitmapSaves();
+ mService.waitForBitmapSavesForTest();
assertBitmapDirectories(USER_0, CALLING_PACKAGE_1, CALLING_PACKAGE_2, CALLING_PACKAGE_3,
"a.b.c", "d.e.f");
@@ -1111,7 +1111,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
// The below check is the same as above, except this time USER_0 use the CALLING_PACKAGE_3
// directory.
- mService.waitForBitmapSaves();
+ mService.waitForBitmapSavesForTest();
assertBitmapDirectories(USER_0, CALLING_PACKAGE_1, CALLING_PACKAGE_2, CALLING_PACKAGE_3);
assertBitmapDirectories(USER_10, CALLING_PACKAGE_1, CALLING_PACKAGE_2);
@@ -1390,7 +1390,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
.setIcon(Icon.createWithContentUri("test_uri"))
.build()
)));
- mService.waitForBitmapSaves();
+ mService.waitForBitmapSavesForTest();
assertWith(getCallerShortcuts())
.forShortcutWithId("s1", si -> {
assertTrue(si.hasIconUri());
@@ -1402,13 +1402,13 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
.setIcon(Icon.createWithResource(getTestContext(), R.drawable.black_32x32))
.build()
)));
- mService.waitForBitmapSaves();
+ mService.waitForBitmapSavesForTest();
assertWith(getCallerShortcuts())
.forShortcutWithId("s1", si -> {
assertTrue(si.hasIconResource());
assertEquals(R.drawable.black_32x32, si.getIconResourceId());
});
- mService.waitForBitmapSaves();
+ mService.waitForBitmapSavesForTest();
mInjectedCurrentTimeMillis += INTERVAL; // reset throttling
@@ -1419,7 +1419,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
getTestContext().getResources(), R.drawable.black_64x64)))
.build()
)));
- mService.waitForBitmapSaves();
+ mService.waitForBitmapSavesForTest();
assertWith(getCallerShortcuts())
.forShortcutWithId("s1", si -> {
assertTrue(si.hasIconFile());
@@ -1437,7 +1437,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
getTestContext().getResources(), R.drawable.black_64x64)))
.build()
)));
- mService.waitForBitmapSaves();
+ mService.waitForBitmapSavesForTest();
assertWith(getCallerShortcuts())
.forShortcutWithId("s1", si -> {
assertTrue(si.hasIconFile());
@@ -1451,7 +1451,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
.setIcon(Icon.createWithResource(getTestContext(), R.drawable.black_32x32))
.build()
)));
- mService.waitForBitmapSaves();
+ mService.waitForBitmapSavesForTest();
assertWith(getCallerShortcuts())
.forShortcutWithId("s1", si -> {
assertTrue(si.hasIconResource());
@@ -1463,7 +1463,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
.setIcon(Icon.createWithContentUri("test_uri"))
.build()
)));
- mService.waitForBitmapSaves();
+ mService.waitForBitmapSavesForTest();
assertWith(getCallerShortcuts())
.forShortcutWithId("s1", si -> {
assertTrue(si.hasIconUri());
diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
index 20486b3e396d..8167b44ee59d 100644
--- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
@@ -454,14 +454,14 @@ public class SystemConfigTest {
+ " <library \n"
+ " name=\"foo\"\n"
+ " file=\"" + mFooJar + "\"\n"
- + " on-bootclasspath-before=\"Q\"\n"
+ + " on-bootclasspath-before=\"A\"\n"
+ " on-bootclasspath-since=\"W\"\n"
+ " />\n\n"
+ " </permissions>";
parseSharedLibraries(contents);
assertFooIsOnlySharedLibrary();
SystemConfig.SharedLibraryEntry entry = mSysConfig.getSharedLibraries().get("foo");
- assertThat(entry.onBootclasspathBefore).isEqualTo("Q");
+ assertThat(entry.onBootclasspathBefore).isEqualTo("A");
assertThat(entry.onBootclasspathSince).isEqualTo("W");
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 348e015500fe..c0cd7a755e25 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -198,6 +198,7 @@ import com.android.internal.app.IAppOpsService;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.logging.InstanceIdSequence;
import com.android.internal.logging.InstanceIdSequenceFake;
+import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.server.DeviceIdleInternal;
import com.android.server.LocalServices;
@@ -303,6 +304,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
ActivityManagerInternal mAmi;
@Mock
private Looper mMainLooper;
+ @Mock
+ private NotificationManager mMockNm;
@Mock
IIntentSender pi1;
@@ -405,6 +408,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
LocalServices.removeServiceForTest(PermissionPolicyInternal.class);
LocalServices.addService(PermissionPolicyInternal.class, mPermissionPolicyInternal);
mContext.addMockSystemService(Context.ALARM_SERVICE, mAlarmManager);
+ mContext.addMockSystemService(NotificationManager.class, mMockNm);
doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any());
@@ -7516,46 +7520,53 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- public void testOnBubbleNotificationSuppressionChanged() throws Exception {
+ public void testOnBubbleMetadataFlagChanged() throws Exception {
setUpPrefsForBubbles(PKG, mUid,
true /* global */,
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
- // Bubble notification
+ // Post a bubble notification
NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, "tag");
-
+ // Set this so that the bubble can be suppressed
+ nr.getNotification().getBubbleMetadata().setFlags(
+ Notification.BubbleMetadata.FLAG_SUPPRESSABLE_BUBBLE);
mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
waitForIdle();
- // NOT suppressed
+ // Check the flags
Notification n = mBinderService.getActiveNotifications(PKG)[0].getNotification();
assertFalse(n.getBubbleMetadata().isNotificationSuppressed());
+ assertFalse(n.getBubbleMetadata().getAutoExpandBubble());
+ assertFalse(n.getBubbleMetadata().isBubbleSuppressed());
+ assertTrue(n.getBubbleMetadata().isBubbleSuppressable());
// Reset as this is called when the notif is first sent
reset(mListeners);
- // Test: update suppression to true
- mService.mNotificationDelegate.onBubbleNotificationSuppressionChanged(nr.getKey(), true,
- false);
+ // Test: change the flags
+ int flags = Notification.BubbleMetadata.FLAG_SUPPRESSABLE_BUBBLE;
+ flags |= Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE;
+ flags |= Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
+ flags |= Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE;
+ mService.mNotificationDelegate.onBubbleMetadataFlagChanged(nr.getKey(), flags);
waitForIdle();
// Check
n = mBinderService.getActiveNotifications(PKG)[0].getNotification();
- assertTrue(n.getBubbleMetadata().isNotificationSuppressed());
+ assertEquals(flags, n.getBubbleMetadata().getFlags());
// Reset to check again
reset(mListeners);
- // Test: update suppression to false
- mService.mNotificationDelegate.onBubbleNotificationSuppressionChanged(nr.getKey(), false,
- false);
+ // Test: clear flags
+ mService.mNotificationDelegate.onBubbleMetadataFlagChanged(nr.getKey(), 0);
waitForIdle();
// Check
n = mBinderService.getActiveNotifications(PKG)[0].getNotification();
- assertFalse(n.getBubbleMetadata().isNotificationSuppressed());
+ assertEquals(0, n.getBubbleMetadata().getFlags());
}
@Test
@@ -9294,4 +9305,77 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// the notifyPostedLocked function is called twice.
verify(mListeners, times(2)).notifyPostedLocked(any(), any());
}
+
+ @Test
+ public void testMaybeShowReviewPermissionsNotification_unknown() {
+ // Set up various possible states of the settings int and confirm whether or not the
+ // notification is shown as expected
+
+ // Initial state: default/unknown setting, make sure nothing happens
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN);
+ mService.maybeShowInitialReviewPermissionsNotification();
+ verify(mMockNm, never()).notify(anyString(), anyInt(), any(Notification.class));
+ }
+
+ @Test
+ public void testMaybeShowReviewPermissionsNotification_shouldShow() {
+ // If state is SHOULD_SHOW, it ... should show
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW);
+ mService.maybeShowInitialReviewPermissionsNotification();
+ verify(mMockNm, times(1)).notify(eq(NotificationManagerService.TAG),
+ eq(SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS),
+ any(Notification.class));
+ }
+
+ @Test
+ public void testMaybeShowReviewPermissionsNotification_alreadyShown() {
+ // If state is either USER_INTERACTED or DISMISSED, we should not show this on boot
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED);
+ mService.maybeShowInitialReviewPermissionsNotification();
+
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_DISMISSED);
+ mService.maybeShowInitialReviewPermissionsNotification();
+
+ verify(mMockNm, never()).notify(anyString(), anyInt(), any(Notification.class));
+ }
+
+ @Test
+ public void testMaybeShowReviewPermissionsNotification_reshown() {
+ // If we have re-shown the notification and the user did not subsequently interacted with
+ // it, then make sure we show when trying on boot
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN);
+ mService.maybeShowInitialReviewPermissionsNotification();
+ verify(mMockNm, times(1)).notify(eq(NotificationManagerService.TAG),
+ eq(SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS),
+ any(Notification.class));
+ }
+
+ @Test
+ public void testRescheduledReviewPermissionsNotification() {
+ // when rescheduled, the notification goes through the NotificationManagerInternal service
+ // this call doesn't need to know anything about previously scheduled state -- if called,
+ // it should send the notification & write the appropriate int to Settings
+ mInternalService.sendReviewPermissionsNotification();
+
+ // Notification should be sent
+ verify(mMockNm, times(1)).notify(eq(NotificationManagerService.TAG),
+ eq(SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS),
+ any(Notification.class));
+
+ // write STATE_RESHOWN to settings
+ assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN,
+ Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 63d7453450d2..6d0895935877 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -289,6 +289,11 @@ public class PreferencesHelperTest extends UiServiceTestCase {
.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
.setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
.build();
+
+ // make sure that the settings for review notification permissions are unset to begin with
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN);
}
private ByteArrayOutputStream writeXmlAndPurge(
@@ -656,6 +661,13 @@ public class PreferencesHelperTest extends UiServiceTestCase {
verify(mPermissionHelper).setNotificationPermission(nMr1Expected);
verify(mPermissionHelper).setNotificationPermission(oExpected);
verify(mPermissionHelper).setNotificationPermission(pExpected);
+
+ // verify that we also write a state for review_permissions_notification to eventually
+ // show a notification
+ assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW,
+ Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
}
@Test
@@ -738,7 +750,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
}
@Test
- public void testReadXml_newXml_noMigration() throws Exception {
+ public void testReadXml_newXml_noMigration_showPermissionNotification() throws Exception {
when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory);
@@ -786,6 +798,70 @@ public class PreferencesHelperTest extends UiServiceTestCase {
compareChannels(idp, mHelper.getNotificationChannel(PKG_P, UID_P, idp.getId(), false));
verify(mPermissionHelper, never()).setNotificationPermission(any());
+
+ // verify that we do, however, write a state for review_permissions_notification to
+ // eventually show a notification, since this XML version is older than the notification
+ assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW,
+ Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
+ }
+
+ @Test
+ public void testReadXml_newXml_noMigration_noPermissionNotification() throws Exception {
+ when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory);
+
+ String xml = "<ranking version=\"4\">\n"
+ + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
+ + "<channel id=\"idn\" name=\"name\" importance=\"2\"/>\n"
+ + "<channel id=\"miscellaneous\" name=\"Uncategorized\" />\n"
+ + "</package>\n"
+ + "<package name=\"" + PKG_O + "\" >\n"
+ + "<channel id=\"ido\" name=\"name2\" importance=\"2\" show_badge=\"true\"/>\n"
+ + "</package>\n"
+ + "<package name=\"" + PKG_P + "\" >\n"
+ + "<channel id=\"idp\" name=\"name3\" importance=\"4\" locked=\"2\" />\n"
+ + "</package>\n"
+ + "</ranking>\n";
+ NotificationChannel idn = new NotificationChannel("idn", "name", IMPORTANCE_LOW);
+ idn.setSound(null, new AudioAttributes.Builder()
+ .setUsage(USAGE_NOTIFICATION)
+ .setContentType(CONTENT_TYPE_SONIFICATION)
+ .setFlags(0)
+ .build());
+ idn.setShowBadge(false);
+ NotificationChannel ido = new NotificationChannel("ido", "name2", IMPORTANCE_LOW);
+ ido.setShowBadge(true);
+ ido.setSound(null, new AudioAttributes.Builder()
+ .setUsage(USAGE_NOTIFICATION)
+ .setContentType(CONTENT_TYPE_SONIFICATION)
+ .setFlags(0)
+ .build());
+ NotificationChannel idp = new NotificationChannel("idp", "name3", IMPORTANCE_HIGH);
+ idp.lockFields(2);
+ idp.setSound(null, new AudioAttributes.Builder()
+ .setUsage(USAGE_NOTIFICATION)
+ .setContentType(CONTENT_TYPE_SONIFICATION)
+ .setFlags(0)
+ .build());
+
+ loadByteArrayXml(xml.getBytes(), true, USER_SYSTEM);
+
+ assertTrue(mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
+
+ assertEquals(idn, mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, idn.getId(), false));
+ compareChannels(ido, mHelper.getNotificationChannel(PKG_O, UID_O, ido.getId(), false));
+ compareChannels(idp, mHelper.getNotificationChannel(PKG_P, UID_P, idp.getId(), false));
+
+ verify(mPermissionHelper, never()).setNotificationPermission(any());
+
+ // this XML is new enough, we should not be attempting to show a notification or anything
+ assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN,
+ Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
}
@Test
@@ -903,7 +979,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
ByteArrayOutputStream baos = writeXmlAndPurge(
PKG_N_MR1, UID_N_MR1, false, USER_SYSTEM);
- String expected = "<ranking version=\"3\">\n"
+ String expected = "<ranking version=\"4\">\n"
+ "<package name=\"com.example.o\" show_badge=\"true\" "
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
+ "sent_valid_msg=\"false\" user_demote_msg_app=\"false\" uid=\"1111\">\n"
@@ -984,7 +1060,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
ByteArrayOutputStream baos = writeXmlAndPurge(
PKG_N_MR1, UID_N_MR1, true, USER_SYSTEM);
- String expected = "<ranking version=\"3\">\n"
+ String expected = "<ranking version=\"4\">\n"
// Importance 0 because off in permissionhelper
+ "<package name=\"com.example.o\" importance=\"0\" show_badge=\"true\" "
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
@@ -1067,7 +1143,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
ByteArrayOutputStream baos = writeXmlAndPurge(
PKG_N_MR1, UID_N_MR1, true, USER_SYSTEM);
- String expected = "<ranking version=\"3\">\n"
+ String expected = "<ranking version=\"4\">\n"
// Importance 0 because off in permissionhelper
+ "<package name=\"com.example.o\" importance=\"0\" show_badge=\"true\" "
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
@@ -1121,7 +1197,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
ByteArrayOutputStream baos = writeXmlAndPurge(
PKG_N_MR1, UID_N_MR1, true, USER_SYSTEM);
- String expected = "<ranking version=\"3\">\n"
+ String expected = "<ranking version=\"4\">\n"
// Packages that exist solely in permissionhelper
+ "<package name=\"" + PKG_P + "\" importance=\"3\" />\n"
+ "<package name=\"" + PKG_O + "\" importance=\"0\" />\n"
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java
new file mode 100644
index 000000000000..5a4ce5da676e
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.notification;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.rule.ServiceTestRule;
+
+import com.android.server.LocalServices;
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+
+@RunWith(AndroidTestingRunner.class)
+public class ReviewNotificationPermissionsJobServiceTest extends UiServiceTestCase {
+ private ReviewNotificationPermissionsJobService mJobService;
+ private JobParameters mJobParams = new JobParameters(null,
+ ReviewNotificationPermissionsJobService.JOB_ID, null, null, null,
+ 0, false, false, null, null, null);
+
+ @Captor
+ ArgumentCaptor<JobInfo> mJobInfoCaptor;
+
+ @Mock
+ private JobScheduler mMockJobScheduler;
+
+ @Mock
+ private NotificationManagerInternal mMockNotificationManagerInternal;
+
+ @Rule
+ public final ServiceTestRule mServiceRule = new ServiceTestRule();
+
+ @Before
+ public void setUp() throws Exception {
+ mJobService = new ReviewNotificationPermissionsJobService();
+ mContext.addMockSystemService(JobScheduler.class, mMockJobScheduler);
+
+ // add NotificationManagerInternal to LocalServices
+ LocalServices.removeServiceForTest(NotificationManagerInternal.class);
+ LocalServices.addService(NotificationManagerInternal.class,
+ mMockNotificationManagerInternal);
+ }
+
+ @Test
+ public void testScheduleJob() {
+ // if asked, the job doesn't currently exist yet
+ when(mMockJobScheduler.getPendingJob(anyInt())).thenReturn(null);
+
+ final int rescheduleTimeMillis = 350; // arbitrary number
+
+ // attempt to schedule the job
+ ReviewNotificationPermissionsJobService.scheduleJob(mContext, rescheduleTimeMillis);
+ verify(mMockJobScheduler, times(1)).schedule(mJobInfoCaptor.capture());
+
+ // verify various properties of the job that is passed in to the job scheduler
+ JobInfo jobInfo = mJobInfoCaptor.getValue();
+ assertEquals(ReviewNotificationPermissionsJobService.JOB_ID, jobInfo.getId());
+ assertEquals(rescheduleTimeMillis, jobInfo.getMinLatencyMillis());
+ assertTrue(jobInfo.isPersisted()); // should continue after reboot
+ assertFalse(jobInfo.isPeriodic()); // one time
+ }
+
+ @Test
+ public void testOnStartJob() {
+ // the job need not be persisted after it does its work, so it'll return
+ // false
+ assertFalse(mJobService.onStartJob(mJobParams));
+
+ // verify that starting the job causes the notification to be sent
+ verify(mMockNotificationManagerInternal).sendReviewPermissionsNotification();
+ }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsReceiverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsReceiverTest.java
new file mode 100644
index 000000000000..12281a742a50
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsReceiverTest.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class ReviewNotificationPermissionsReceiverTest extends UiServiceTestCase {
+
+ // Simple mock class that just overrides the reschedule and cancel behavior so that it's easy
+ // to tell whether the receiver has sent requests to either reschedule or cancel the
+ // notification (or both).
+ private class MockReviewNotificationPermissionsReceiver
+ extends ReviewNotificationPermissionsReceiver {
+ boolean mCanceled = false;
+ boolean mRescheduled = false;
+
+ @Override
+ protected void cancelNotification(Context context) {
+ mCanceled = true;
+ }
+
+ @Override
+ protected void rescheduleNotification(Context context) {
+ mRescheduled = true;
+ }
+ }
+
+ private MockReviewNotificationPermissionsReceiver mReceiver;
+ private Intent mIntent;
+
+ @Before
+ public void setUp() {
+ mReceiver = new MockReviewNotificationPermissionsReceiver();
+ mIntent = new Intent(); // actions will be set in test cases
+ }
+
+ @Test
+ public void testReceive_remindMeLater_firstTime() {
+ // Test what happens when we receive a "remind me later" intent coming from
+ // a previously-not-interacted notification
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW);
+
+ // set up Intent action
+ mIntent.setAction(NotificationManagerService.REVIEW_NOTIF_ACTION_REMIND);
+
+ // Upon receipt of the intent, the following things should happen:
+ // - notification rescheduled
+ // - notification explicitly canceled
+ // - settings state updated to indicate user has interacted
+ mReceiver.onReceive(mContext, mIntent);
+ assertTrue(mReceiver.mRescheduled);
+ assertTrue(mReceiver.mCanceled);
+ assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED,
+ Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
+ }
+
+ @Test
+ public void testReceive_remindMeLater_laterTimes() {
+ // Test what happens when we receive a "remind me later" intent coming from
+ // a previously-interacted notification that has been rescheduled
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN);
+
+ // set up Intent action
+ mIntent.setAction(NotificationManagerService.REVIEW_NOTIF_ACTION_REMIND);
+
+ // Upon receipt of the intent, the following things should still happen
+ // regardless of the fact that the user has interacted before:
+ // - notification rescheduled
+ // - notification explicitly canceled
+ // - settings state still indicate user has interacted
+ mReceiver.onReceive(mContext, mIntent);
+ assertTrue(mReceiver.mRescheduled);
+ assertTrue(mReceiver.mCanceled);
+ assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED,
+ Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
+ }
+
+ @Test
+ public void testReceive_dismiss() {
+ // Test that dismissing the notification does *not* reschedule the notification,
+ // does cancel it, and writes that it has been dismissed to settings
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW);
+
+ // set up Intent action
+ mIntent.setAction(NotificationManagerService.REVIEW_NOTIF_ACTION_DISMISS);
+
+ // send intent, watch what happens
+ mReceiver.onReceive(mContext, mIntent);
+ assertFalse(mReceiver.mRescheduled);
+ assertTrue(mReceiver.mCanceled);
+ assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_DISMISSED,
+ Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
+ }
+
+ @Test
+ public void testReceive_notificationCanceled_firstSwipe() {
+ // Test the basic swipe away case: the first time the user swipes the notification
+ // away, it will not have been interacted with yet, so make sure it's rescheduled
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW);
+
+ // set up Intent action, would be called from notification's delete intent
+ mIntent.setAction(NotificationManagerService.REVIEW_NOTIF_ACTION_CANCELED);
+
+ // send intent, make sure it gets:
+ // - rescheduled
+ // - not explicitly canceled, the notification was already canceled
+ // - noted that it's been interacted with
+ mReceiver.onReceive(mContext, mIntent);
+ assertTrue(mReceiver.mRescheduled);
+ assertFalse(mReceiver.mCanceled);
+ assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED,
+ Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
+ }
+
+ @Test
+ public void testReceive_notificationCanceled_secondSwipe() {
+ // Test the swipe away case for a rescheduled notification: in this case
+ // it should not be rescheduled anymore
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN);
+
+ // set up Intent action, would be called from notification's delete intent
+ mIntent.setAction(NotificationManagerService.REVIEW_NOTIF_ACTION_CANCELED);
+
+ // send intent, make sure it gets:
+ // - not rescheduled on the second+ swipe
+ // - not explicitly canceled, the notification was already canceled
+ // - mark as user interacted
+ mReceiver.onReceive(mContext, mIntent);
+ assertFalse(mReceiver.mRescheduled);
+ assertFalse(mReceiver.mCanceled);
+ assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED,
+ Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
+ }
+
+ @Test
+ public void testReceive_notificationCanceled_fromDismiss() {
+ // Test that if the notification delete intent is called due to us canceling
+ // the notification from the receiver, we don't do anything extra
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_DISMISSED);
+
+ // set up Intent action, would be called from notification's delete intent
+ mIntent.setAction(NotificationManagerService.REVIEW_NOTIF_ACTION_CANCELED);
+
+ // nothing should happen, nothing at all
+ mReceiver.onReceive(mContext, mIntent);
+ assertFalse(mReceiver.mRescheduled);
+ assertFalse(mReceiver.mCanceled);
+ assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_DISMISSED,
+ Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+ NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index f59ec42a4a71..b9432753c17f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -785,6 +785,34 @@ public class ActivityStarterTests extends WindowTestsBase {
}
/**
+ * This test ensures that {@link ActivityStarter#setTargetRootTaskIfNeeded} will task the
+ * adjacent task of indicated launch target into account. So the existing task will be launched
+ * into closer target.
+ */
+ @Test
+ public void testAdjustLaunchTargetWithAdjacentTask() {
+ // Create adjacent tasks and put one activity under it
+ final Task parent = new TaskBuilder(mSupervisor).build();
+ final Task adjacentParent = new TaskBuilder(mSupervisor).build();
+ parent.setAdjacentTaskFragment(adjacentParent, true);
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setParentTask(parent)
+ .setCreateTask(true).build();
+
+ // Launch the activity to its adjacent parent
+ final ActivityOptions options = ActivityOptions.makeBasic()
+ .setLaunchRootTask(adjacentParent.mRemoteToken.toWindowContainerToken());
+ prepareStarter(FLAG_ACTIVITY_NEW_TASK, false /* mockGetRootTask */)
+ .setReason("testAdjustLaunchTargetWithAdjacentTask")
+ .setIntent(activity.intent)
+ .setActivityOptions(options.toBundle())
+ .execute();
+
+ // Verify the activity will be launched into the original parent
+ assertTrue(activity.isDescendantOf(parent));
+ }
+
+ /**
* This test ensures that {@link ActivityStarter#setTargetRootTaskIfNeeded} will
* move the existing task to front if the current focused root task doesn't have running task.
*/
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index 49cd343ef4af..873d9f3fc023 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -306,6 +306,7 @@ public class BackNavigationControllerTests extends WindowTestsBase {
if (focus) {
doReturn(window.getWindowInfo().token)
.when(mWindowManagerInternal).getFocusedWindowToken();
+ doReturn(window).when(mWm).getFocusedWindowLocked();
}
}
}
diff --git a/services/voiceinteraction/TEST_MAPPING b/services/voiceinteraction/TEST_MAPPING
index bc8c639df1c1..22a6445843a4 100644
--- a/services/voiceinteraction/TEST_MAPPING
+++ b/services/voiceinteraction/TEST_MAPPING
@@ -5,19 +5,6 @@
"options": [
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
- },
- // TODO(b/225076204): Remove the following four test cases after fixing the test fail.
- {
- "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_createDetectorTwiceQuickly_triggerSuccess"
- },
- {
- "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_onDetectFromExternalSource_success"
- },
- {
- "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_createDetectorTwiceQuickly_triggerSuccess"
- },
- {
- "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_onDetectFromMic_success"
}
]
},
diff --git a/telecomm/java/android/telecom/PhoneAccountHandle.java b/telecomm/java/android/telecom/PhoneAccountHandle.java
index e3485deb9080..ec94f8a1829f 100644
--- a/telecomm/java/android/telecom/PhoneAccountHandle.java
+++ b/telecomm/java/android/telecom/PhoneAccountHandle.java
@@ -46,6 +46,14 @@ import java.util.Objects;
* See {@link PhoneAccount}, {@link TelecomManager}.
*/
public final class PhoneAccountHandle implements Parcelable {
+ /**
+ * Expected component name of Telephony phone accounts; ONLY used to determine if we should log
+ * the phone account handle ID.
+ */
+ private static final ComponentName TELEPHONY_COMPONENT_NAME =
+ new ComponentName("com.android.phone",
+ "com.android.services.telephony.TelephonyConnectionService");
+
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 127403196)
private final ComponentName mComponentName;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
@@ -136,14 +144,23 @@ public final class PhoneAccountHandle implements Parcelable {
@Override
public String toString() {
- // Note: Log.pii called for mId as it can contain personally identifying phone account
- // information such as SIP account IDs.
- return new StringBuilder().append(mComponentName)
- .append(", ")
- .append(Log.pii(mId))
- .append(", ")
- .append(mUserHandle)
- .toString();
+ StringBuilder sb = new StringBuilder()
+ .append(mComponentName)
+ .append(", ");
+
+ if (TELEPHONY_COMPONENT_NAME.equals(mComponentName)) {
+ // Telephony phone account handles are now keyed by subscription id which is not
+ // sensitive.
+ sb.append(mId);
+ } else {
+ // Note: Log.pii called for mId as it can contain personally identifying phone account
+ // information such as SIP account IDs.
+ sb.append(Log.pii(mId));
+ }
+ sb.append(", ");
+ sb.append(mUserHandle);
+
+ return sb.toString();
}
@Override
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index e21301eb32af..70fe6b10ef20 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -4980,6 +4980,25 @@ public class CarrierConfigManager {
KEY_PREFIX + "use_sip_uri_for_presence_subscribe_bool";
/**
+ * Flag indicating whether or not to use TEL URI when setting the entity uri field and
+ * contact element of each tuple.
+ *
+ * When {@code true}, the device sets the entity uri field and contact element to be
+ * TEL URI. This is done by first searching for the first TEL URI provided in
+ * p-associated-uri header. If there are no TEL URIs in the p-associated-uri header, we will
+ * convert the first SIP URI provided in the header to a TEL URI. If there are no URIs in
+ * the p-associated-uri header, we will then fall back to using the SIM card to generate the
+ * TEL URI.
+ * If {@code false}, the first URI provided in the p-associated-uri header is used,
+ * independent of the URI scheme. If there are no URIs available from p-associated-uri
+ * header, we will try to generate a SIP URI or TEL URI from the information provided by the
+ * SIM card, depending on the information available.
+ * @hide
+ */
+ public static final String KEY_USE_TEL_URI_FOR_PIDF_XML_BOOL =
+ KEY_PREFIX + "use_tel_uri_for_pidf_xml";
+
+ /**
* An integer key associated with the period of time in seconds the non-rcs capability
* information of each contact is cached on the device.
* <p>
diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java
index fa6de1ad2864..ac1f376cf6b5 100644
--- a/telephony/java/android/telephony/DataFailCause.java
+++ b/telephony/java/android/telephony/DataFailCause.java
@@ -1673,4 +1673,9 @@ public final class DataFailCause {
return UNKNOWN;
}
}
+
+ /** @hide */
+ public static boolean isFailCauseExisting(@DataFailureCause int failCause) {
+ return sFailCauseMap.containsKey(failCause);
+ }
}
diff --git a/tests/ApkVerityTest/Android.bp b/tests/ApkVerityTest/Android.bp
index d4fa1dda8bdf..62e16a5b83de 100644
--- a/tests/ApkVerityTest/Android.bp
+++ b/tests/ApkVerityTest/Android.bp
@@ -37,8 +37,8 @@ java_test_host {
"general-tests",
"vts",
],
- data_device_bins: [
- "block_device_writer",
+ target_required: [
+ "block_device_writer_module",
],
data: [
":ApkVerityTestCertDer",
diff --git a/tests/ApkVerityTest/block_device_writer/Android.bp b/tests/ApkVerityTest/block_device_writer/Android.bp
index e5d009dc10fd..fdfa41fd4ca9 100644
--- a/tests/ApkVerityTest/block_device_writer/Android.bp
+++ b/tests/ApkVerityTest/block_device_writer/Android.bp
@@ -24,7 +24,12 @@ package {
}
cc_test {
- name: "block_device_writer",
+ // Depending on how the test runs, the executable may be uploaded to different location.
+ // Before the bug in the file pusher is fixed, workaround by making the name unique.
+ // See b/124718249#comment12.
+ name: "block_device_writer_module",
+ stem: "block_device_writer",
+
srcs: ["block_device_writer.cpp"],
cflags: [
"-D_FILE_OFFSET_BITS=64",
@@ -37,7 +42,22 @@ cc_test {
"libbase",
"libutils",
],
- compile_multilib: "first",
+ // For some reasons, cuttlefish (x86) uses x86_64 test suites for testing. Unfortunately, when
+ // the uploader does not pick up the executable from correct output location. The following
+ // workaround allows the test to:
+ // * upload the 32-bit exectuable for both 32 and 64 bits devices to use
+ // * refer to the same executable name in Java
+ // * no need to force the Java test to be archiecture specific.
+ //
+ // See b/145573317 for details.
+ multilib: {
+ lib32: {
+ suffix: "",
+ },
+ lib64: {
+ suffix: "64", // not really used
+ },
+ },
auto_gen_config: false,
test_suites: [
diff --git a/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java b/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
index 730daf32f20d..5c2c15b22bb0 100644
--- a/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
+++ b/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
@@ -32,7 +32,7 @@ import java.util.ArrayList;
* <p>To use this class, please push block_device_writer binary to /data/local/tmp.
* 1. In Android.bp, add:
* <pre>
- * data_device_bins: ["block_device_writer"],
+ * target_required: ["block_device_writer_module"],
* </pre>
* 2. In AndroidText.xml, add:
* <pre>
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt
new file mode 100644
index 000000000000..16c4c254f9e3
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.helpers
+
+import android.app.Instrumentation
+import android.support.test.launcherhelper.ILauncherStrategy
+import android.support.test.launcherhelper.LauncherStrategyFactory
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.parser.toFlickerComponent
+
+class ImeStateInitializeHelper @JvmOverloads constructor(
+ instr: Instrumentation,
+ launcherName: String = ActivityOptions.IME_ACTIVITY_INITIALIZE_LAUNCHER_NAME,
+ component: FlickerComponentName =
+ ActivityOptions.IME_ACTIVITY_INITIALIZE_COMPONENT_NAME.toFlickerComponent(),
+ launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
+ .getInstance(instr)
+ .launcherStrategy
+) : StandardAppHelper(instr, launcherName, component, launcherStrategy) \ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
index 964390930d54..b897ca2a9c15 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
@@ -28,6 +28,7 @@ import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.helpers.ImeStateInitializeHelper
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.traces.common.FlickerComponentName
import org.junit.FixMethodOrder
@@ -71,17 +72,20 @@ import org.junit.runners.Parameterized
class LaunchAppShowImeOnStartTest(private val testSpec: FlickerTestParameter) {
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
+ private val initializeApp = ImeStateInitializeHelper(instrumentation)
@FlickerBuilderProvider
fun buildFlicker(): FlickerBuilder {
return FlickerBuilder(instrumentation).apply {
setup {
eachRun {
+ initializeApp.launchViaIntent()
this.setRotation(testSpec.startRotation)
}
}
teardown {
eachRun {
+ initializeApp.exit()
testApp.exit()
}
}
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 28422321f5b6..43aa4b151548 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -52,6 +52,16 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+ <activity android:name=".ImeStateInitializeActivity"
+ android:theme="@style/no_starting_window"
+ android:windowSoftInputMode="stateAlwaysHidden"
+ android:label="ImeStateInitializeActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
<activity android:name=".SeamlessRotationActivity"
android:taskAffinity="com.android.server.wm.flicker.testapp.SeamlessRotationActivity"
android:theme="@style/CutoutShortEdges"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
index 589df38eec83..1d21fd56a487 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
@@ -42,4 +42,8 @@
<item name="android:backgroundDimEnabled">false</item>
<item name="android:windowSoftInputMode">stateUnchanged</item>
</style>
+
+ <style name="no_starting_window" parent="@android:style/Theme.DeviceDefault">
+ <item name="android:windowDisablePreview">true</item>
+ </style>
</resources>
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index e080709038ff..6cda482dd30a 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -37,6 +37,11 @@ public class ActivityOptions {
new ComponentName(FLICKER_APP_PACKAGE,
FLICKER_APP_PACKAGE + ".ImeActivity");
+ public static final String IME_ACTIVITY_INITIALIZE_LAUNCHER_NAME = "ImeStateInitializeActivity";
+ public static final ComponentName IME_ACTIVITY_INITIALIZE_COMPONENT_NAME =
+ new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".ImeStateInitializeActivity");
+
public static final String SIMPLE_ACTIVITY_LAUNCHER_NAME = "SimpleApp";
public static final ComponentName SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME =
new ComponentName(FLICKER_APP_PACKAGE,
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeStateInitializeActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeStateInitializeActivity.java
new file mode 100644
index 000000000000..4be79c4f7bad
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeStateInitializeActivity.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.testapp;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A nop {@link Activity} to make sure that the test starts from a deterministic state.
+ *
+ * <p>Currently this {@link Activity} makes sure the following things</p>
+ * <li>
+ * <ul>Hide the software keyboard with
+ * {@link android.view.WindowManager.LayoutParams#SOFT_INPUT_STATE_ALWAYS_HIDDEN}</ul>
+ * <ul>Make sure that the navigation bar (if supported) is rendered with {@link Color#BLACK}.
+ * </ul>
+ * </li>
+ */
+public class ImeStateInitializeActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+ final View view = new View(this);
+ view.setBackgroundColor(Color.WHITE);
+ view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+
+ // Make sure that navigation bar is rendered with black (if supported).
+ getWindow().setNavigationBarColor(Color.BLACK);
+
+ setContentView(view);
+ }
+}
diff --git a/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt b/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt
index 3c6d54d24291..ae722477a2bc 100644
--- a/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt
+++ b/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt
@@ -29,7 +29,7 @@ import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import androidx.test.uiautomator.UiDevice
-import com.google.common.truth.Truth.assertThat
+import android.trust.test.lib.wait
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -74,9 +74,9 @@ class TemporaryAndRenewableTrustTest {
uiDevice.sleep()
lockStateTrackingRule.assertLocked()
+ uiDevice.wakeUp()
trustAgentRule.agent.grantTrust(
GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {}
- uiDevice.wakeUp()
lockStateTrackingRule.assertLocked()
}
@@ -98,9 +98,9 @@ class TemporaryAndRenewableTrustTest {
lockStateTrackingRule.assertLocked()
+ uiDevice.wakeUp()
trustAgentRule.agent.grantTrust(
GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {}
- uiDevice.wakeUp()
lockStateTrackingRule.assertUnlocked()
}
@@ -116,6 +116,7 @@ class TemporaryAndRenewableTrustTest {
uiDevice.sleep()
lockStateTrackingRule.assertLocked()
+ uiDevice.wakeUp()
Log.i(TAG, "Renewing trust and unlocking")
var result: GrantTrustResult? = null
@@ -124,10 +125,9 @@ class TemporaryAndRenewableTrustTest {
Log.i(TAG, "Callback received; status=${it.status}")
result = it
}
- uiDevice.wakeUp()
lockStateTrackingRule.assertUnlocked()
- assertThat(result?.status).isEqualTo(STATUS_UNLOCKED_BY_GRANT)
+ wait("callback triggered") { result?.status == STATUS_UNLOCKED_BY_GRANT }
}
@Test
@@ -141,7 +141,6 @@ class TemporaryAndRenewableTrustTest {
trustAgentRule.agent.revokeTrust()
await(500)
uiDevice.wakeUp()
- await(500)
trustAgentRule.agent.grantTrust(
GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {}